3 コミット d642a71e7c ... 190fa486c9

作者 SHA1 メッセージ 日付
  makstravm 190fa486c9 added recursive functions for view comments 2 年 前
  makstravm 1d5a844cf9 added page Collection, refactoring 2 年 前
  makstravm 0e77954743 added preloader and messege added error message when login or password is not correct 2 年 前

+ 1 - 0
package.json

@@ -4,6 +4,7 @@
     "private": true,
     "dependencies": {
         "@ant-design/icons": "^4.7.0",
+        "@babel/plugin-syntax-dynamic-import": "^7.8.3",
         "@testing-library/jest-dom": "^5.16.1",
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",

+ 8 - 5
src/App.js

@@ -8,12 +8,14 @@ import { Content, Main } from './pages/Content';
 import { CProfilePage } from './pages/ProfilePage';
 import HeaderComponent from './pages/Header';
 import { CMainPostsFeed } from './pages/MainPostsFeed';
-import { CRRoute } from './helpers';
+import { CRRoute, promiseName } from './helpers';
 import { CPostPage } from './pages/PostPage';
 import { CAllPosts } from './pages/AllPosts';
 import { CEntityEditorPost } from './pages/EntityEditorPost';
 import { SettingsPage } from './pages/SettingsPage';
 import { Authorization } from './pages/Authorization';
+import { CPreloader } from './pages/Preloader';
+import { CCollectionPage } from './pages/CollectionPage';
 
 export const history = createHistory()
 
@@ -31,15 +33,16 @@ const AppContent = ({ isToken }) =>
                 <HeaderComponent />
                 <Main>
                     <Switch>
-                        <Route path='/' component={CMainPostsFeed} exact />
+                        <Route path='/feed' component={CMainPostsFeed} />
                         <Route path='/profile/:_id' component={CProfilePage} />
                         {/* <Route path='/message' component={Aside} /> */}
-                        <Route path='/edit/post/:_id' component={CEntityEditorPost} />
+                        <Route path='/edit/post/new' component={CEntityEditorPost} exact />
+                        <Route path='/edit/post/:_id' component={CEntityEditorPost}/>
                         <Route path='/my-settings' component={SettingsPage} />
                         <Route path='/all' component={CAllPosts} />
+                        <Route path='/my-collection' component={CCollectionPage} />
                         <CRRoute path='/post/:id' component={CPostPage} />
-                        <Redirect from='/*' to='/' />
-                        {/* <Redirect from='/*' to='/post/:id' /> */}
+                        <Redirect from='/*' to='/feed' />
                     </Switch>
                 </Main>
             </Content >

+ 42 - 2
src/App.scss

@@ -104,6 +104,12 @@ video {
             display: flex;
             align-items: center;
             justify-content: center;
+
+            &.active {
+                svg {
+                    fill: $defaultColorB;
+                }
+            }
         }
         svg {
             width: 24px;
@@ -112,6 +118,19 @@ video {
     }
 }
 
+.PreloaderImg {
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    z-index: 2;
+    background-color: rgba(#fff, 0.6);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
 .ant-popover {
     padding-top: 12px;
     &-arrow {
@@ -140,6 +159,13 @@ video {
     a {
         transition: all 0.3s;
     }
+    &__icon {
+        margin-right: 2px;
+        svg {
+            width: 15px;
+            height: 15px;
+        }
+    }
     button {
         cursor: pointer;
         text-align-last: left;
@@ -190,6 +216,7 @@ video {
 }
 
 .Main {
+    position: relative;
     padding-top: 58px;
     &__inner {
         padding-top: 22px;
@@ -244,6 +271,7 @@ video {
         top: 1px;
         width: 50px;
         transition: 0.4s;
+        z-index: 5;
         cursor: pointer;
         svg {
             fill: $defaultColorW;
@@ -267,8 +295,10 @@ video {
             opacity: 1;
         }
     }
-    &__heart {
+    &__heart,
+    &__collection {
         button {
+            display: inline-block;
             min-width: auto;
             width: 30px;
             height: 30px;
@@ -277,7 +307,17 @@ video {
             box-shadow: none;
         }
         strong {
-            display: block;
+            display: inline-block;
+        }
+    }
+    &__collection {
+        button {
+            margin-right: auto;
+        }
+        svg {
+            stroke-width: 2px;
+            width: 24px;
+            height: 24px;
         }
     }
     &__comments {

+ 55 - 13
src/actions/index.js

@@ -34,9 +34,10 @@ export const actionRegister = (login, password) =>
 
 //*************** Action ABOUT ME ******************//
 
-
 export const actionAboutMeAC = (data) => ({ type: 'ABOUTME-DATA-ADD', data })
 export const actionFullAboutMe = () => ({ type: 'ABOUT_ME' })
+export const actionRemoveMyDataAC = () => ({ type: 'REMOVE-MYDATA' })
+
 
 export const actionFullAboutMeUpsert = (nick, login) => ({ type: 'ABOUT_ME_UPSERT', nick, login })
 
@@ -111,8 +112,8 @@ export const actionProfileData = (_id) =>
                             avatar { _id url }     
                             createdAt
                             followers {_id }
-                            following {_id }
-                }
+                            following {_id }}
+                
             } `, { id: JSON.stringify([{ _id }]) }))
 
 export const actionProfilePagePost = (_id, skip) => actionPromise('userOneDataPosts', gql(` query userOned($id:String!){
@@ -212,6 +213,7 @@ export const actionMyLikePost = (findId) =>
 
 //****************---Action Like Comment ---*************************//
 
+
 export const actionAddLikeCommentAC = (findId, newResult) => ({ type: 'ADD-LIKE-COMMENT', findId, newResult })
 export const actionRemoveLikeCommentAC = (findId, newResult) => ({ type: 'REMOVE-LIKE-COMMENT', findId, newResult })
 
@@ -238,6 +240,42 @@ export const actionFindLikeComment = (findId) =>
     }`, { id: JSON.stringify([{ _id: findId }]) }))
 
 
+//****************---Collection---*************************//
+
+
+export const actionUpsertCollectionAC = (data) => ({ type: 'UPSERT-COLLECTION', data })
+export const actionHandlerUpsertCollection = (_id, flag) => ({ type: 'HANDLER_UPSERT_COLLECTION', _id, flag })
+
+export const actionAddPostInCollections = (collectionId, newCollection) =>
+    actionPromise('addInCollections', gql(`mutation addInCollections($collection:CollectionInput ){
+        CollectionUpsert(collection:$collection){
+            _id 
+        }
+    }`, { collection: { _id: collectionId, posts: newCollection } }))
+
+export const actionFindMyCollections = (_id) =>
+    actionPromise('findMyCollections', gql(`query findCollections($id:String! ){
+        CollectionFindOne(query:$id){
+        _id text posts{_id }
+        }
+    }`, { id: JSON.stringify([{ ___owner: _id }]) }))
+
+export const actionFullMyCollectionLoad = () => ({ type: 'LOAD_COLLECTION' })
+
+export const actionOnLoadMyCollection = (_id, skip) =>
+    actionPromise('onLoadMyCollections', gql(`query loadCollections($id:String! ){
+       CollectionFind(query:$id){
+         posts { _id  images{ _id url originalFileName}}
+        }
+    }`, {
+        id: JSON.stringify([{ _id }, {
+            sort: [{ _id: -1 }],
+            skip: [skip || 0],
+            limit: [36]
+        }])
+    }))
+
+//  posts{ images { url _id originalFileName } } 
 //****************---Action Subscribe ---*************************//
 
 
@@ -307,16 +345,19 @@ export const actionFindComment = (findId) =>
     }`, { id: JSON.stringify([{ _id: findId }]) }))
 
 export const actionFindSubComment = (findId) =>
-    actionPromise('subComments#' + findId, gql(`query commentFindOne ($id:String!){
+    actionPromise('subComments', gql(`query commentFindOne ($id:String!){
         CommentFindOne(query:$id){
-             answers{
-                _id text createdAt
-                owner{ _id nick login avatar { _id url }} 
-                likes { _id }
-                answerTo {_id text 
+        answers { 
+                _id text
+                post {_id }
+                answers { _id}
+                answerTo{_id owner{login, nick}}
+                likes{_id owner { login nick}}
+                 owner {
+                    _id login nick 
+                    avatar {url} 
+                    } 
                 }
-                answers{ _id }
-            }
         } 
     }`, { id: JSON.stringify([{ _id: findId }]) }))
 
@@ -390,7 +431,7 @@ export const actionGetFindLiked = (_id) =>
 
 export const actionFullSentPost = (images, title, text) => ({ type: 'CREATE_POST', images, text, title })
 
-export const actionSentPost = (upSertPostObj) =>
+export const actionUpsertPost = (upSertPostObj) =>
     actionPromise('sentPost', gql(`mutation sentPost($post: PostInput){
               PostUpsert(post: $post){
                     _id images{_id url originalFileName}
@@ -404,4 +445,5 @@ export const actionSentPost = (upSertPostObj) =>
         // по ансверс.файди найти дочерные коменты и засунуть в массив ансверс как елементы
         // таким оюразом получтьь дерево
         // отдать в рекувсивный компонкнт на отрисовку дерева.
-        // каждый лист дерева приконектить к экшонам лайк и добавление дочернего коммета.Там жк=е должен быть известен пост.айди для создания комментов
+        // каждый лист дерева приконектить к экшонам лайк и добавление дочернего коммета.Там жк=е должен быть известен пост.айди для создания комментов
+

+ 21 - 5
src/components/FormAuthorization.jsx

@@ -1,13 +1,29 @@
-import React from 'react'
+import React, { useEffect } from 'react'
 import { connect } from 'react-redux'
-import { Form, Input, Button, Checkbox } from 'antd';
+import { Form, Input, Button, Checkbox, message } from 'antd';
 import { UserOutlined, LockOutlined } from '@ant-design/icons';
 import { actionFullLogIn, actionFullRegister } from '../actions';
 
-const FormAuthorization = ({ buttonTitle, onSignIn }) => {
+const FormAuthorization = ({ buttonTitle, onSignIn, loginChek }) => {
+
+    useEffect(() => {
+        if (loginChek.status === "RESOLVED" && loginChek.payload === null) {
+            console.log(loginChek?.payload);
+            message.error({
+                content: 'Wrong login or password',
+                className: 'custom-class',
+                style: {
+                    marginTop: '20vh',
+                },
+            })
+        }
+    }, [loginChek.status]);
+
+
     const onFinish = ({ login, password, remember }) => {
         onSignIn(login, password, remember)
     };
+
     return (
         <Form
             name="normal_login"
@@ -62,8 +78,8 @@ const FormAuthorization = ({ buttonTitle, onSignIn }) => {
         </Form>
     )
 }
-export const CLoginForm = connect(null, { onSignIn: actionFullLogIn })(FormAuthorization)
-export const CRegisterForm = connect(null, { onSignIn: actionFullRegister })(FormAuthorization)
+export const CLoginForm = connect(state => ({ loginChek: state?.promise?.login || {} }), { onSignIn: actionFullLogIn, })(FormAuthorization)
+export const CRegisterForm = connect(state => ({ status: state?.promise?.login }), { onSignIn: actionFullRegister, })(FormAuthorization)
 
 
 

+ 27 - 1
src/components/main/Posts.jsx

@@ -32,4 +32,30 @@ const Posts = ({ posts }) =>
         </Col>)
         }
     </Row >
-export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)
+export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)
+
+
+// const Posts = ({ posts }) =>
+//     <Row gutter={[15, 15]}>
+//         {Array.isArray(posts) && posts.map(p => <Col key={p._id} span={8}>
+//             <Link to={`/post/${p._id}`}>
+//                 <Card className='Profile__post' hoverable>
+//                     {p?.url
+//                         ? videoRegExp.test(p?.originalFileName)
+//                             ? <div className='Profile__box' >
+//                                 <video>
+//                                     <source src={backURL + '/' + p?.url} />
+//                                 </video>
+//                                 <PlayCircleOutlined className='Profile__box-icon--video' />
+//                             </div>
+
+//                             : <img src={backURL + '/' + p?.url} />
+
+//                         : <img src={postNoData} />
+//                     }
+//                 </Card>
+//             </Link>
+//         </Col>)
+//         }
+//     </Row >
+// export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)

+ 28 - 13
src/components/main/postsFeed/PostUserPanel.jsx

@@ -2,7 +2,8 @@ import { HeartFilled, HeartOutlined } from "@ant-design/icons"
 import { Button, Col, Row, Tooltip } from "antd"
 import { useState } from "react"
 import { connect } from "react-redux"
-import { actionDelLikePost, actionLikePost } from "../../../actions"
+import { actionDelLikePost, actionHandlerUpsertCollection, actionLikePost, } from "../../../actions"
+import { CollectionEmptySvg, CollectionSvgFill } from "../../../helpers"
 import { CModalPostLiked } from "../profilePage/ModalFollow"
 
 
@@ -18,16 +19,17 @@ const HeartLike = ({ styleFontSize, likeStatus, changeLike }) =>
         }
     />
 
-const PostUserPanel = ({ myID, postId = '', likes = [], styleFontSize, addLikePost, removeLikePost }) => {
+const PostUserPanel = ({ myID, postId = '', likes = [], collections, styleFontSize, addLikePost, removeLikePost, handlerCollection }) => {
     const [open, setOpen] = useState(false)
-    let likeStatus =false
+    let likeStatus = false
     let likeId
     likes.find(l => {
         if (l?.owner?._id === myID) {
             likeStatus = true
             likeId = l._id
-        } 
+        }
     })
+    const flag = collections?.posts.find(c => c._id === postId)
 
     const changeLike = () => likeStatus ? removeLikePost(likeId, postId) : addLikePost(postId)
 
@@ -35,24 +37,37 @@ const PostUserPanel = ({ myID, postId = '', likes = [], styleFontSize, addLikePo
     return (
         <>
             {open && <CModalPostLiked statusModal={setOpen} title={'Liked'} id={postId} />}
-            <Row className="Post__panel-btn" align="middle">
-                <Col className='Post__heart'>
-                    <HeartLike
-                        changeLike={changeLike}
-                        likeStatus={likeStatus}
-                        styleFontSize={styleFontSize} />
+            <Row className="Post__panel-btn" justify="space-between" align="middle">
+                <Col flex={'50%'}>
+                    <Row align="middle" wrap={false}>
+                        <Col className='Post__heart' >
+                            <HeartLike
+                                changeLike={changeLike}
+                                likeStatus={likeStatus}
+                                styleFontSize={styleFontSize} />
+                        </Col>
+                        <Col offset={1}>
+                            {!!likes.length && <button onClick={() => { setOpen(true) }}>Likes:<strong>{` ${likes.length}`}</strong></button>}
+                        </Col>
+                    </Row>
                 </Col>
-                <Col offset={0.5}>
-                    {!!likes.length && <button onClick={() => { setOpen(true) }}>Likes:<strong>{` ${likes.length}`}</strong></button>}
+                <Col flex={'10%'} className='Post__collection'>
+                    <Button type="none"
+                        shape="circle" onClick={() => handlerCollection(postId, flag)}>
+                        {flag ? <CollectionSvgFill /> : <CollectionEmptySvg />}
+                    </Button>
                 </Col>
             </Row>
+
         </>
     )
 }
 
 export const CPostUserPanel = connect(state => ({
-    myID: state.auth.payload.sub.id || ''
+    myID: state.auth.payload.sub.id || '',
+    collections: state.myData?.collections
 }), {
     addLikePost: actionLikePost,
     removeLikePost: actionDelLikePost,
+    handlerCollection: actionHandlerUpsertCollection,
 })(PostUserPanel)

+ 1 - 1
src/components/uploadPhoto/EditDescriptionPost.js

@@ -14,7 +14,7 @@ export const EditDescriptionPost = ({ description, setDescription }) => {
     const [editMode, setEditMode] = useState(false)
 
     useEffect(() => {
-        setText(description || 'Enter descriptin')
+        setText(description || '')
     }, [description]);
 
     const addValueHandler = () => {

+ 8 - 7
src/components/uploadPhoto/EditPhotos.js

@@ -11,11 +11,11 @@ import {
 import { backURL, propsUploadFile, videoRegExp } from "../../helpers";
 
 
-const SortableItemMask = ({ removePhotosItem, setVisible, id }) =>
+const SortableItemMask = ({ removePhotosItem, chekMedia, setVisible, id }) =>
     <div className="SortableItemMask">
-        <Button type="link" onClick={() => setVisible(true)}>
+        {!chekMedia && <Button type="link" onClick={() => setVisible(true)}>
             <EyeOutlined />
-        </Button>
+        </Button>}
         <Button type="link" onClick={() => removePhotosItem(id)}>
             <DeleteOutlined />
         </Button>
@@ -24,15 +24,16 @@ const SortableItemMask = ({ removePhotosItem, setVisible, id }) =>
 
 const Handle = SortableHandle(({ tabIndex, value, removePhotosItem }) => {
     const [visible, setVisible] = useState(false);
+    const chekMedia = videoRegExp.test(value.originalFileName)
     return (
         <div className="Handle" tabIndex={tabIndex} >
-            <SortableItemMask id={value._id} setVisible={setVisible} removePhotosItem={removePhotosItem} />
-            {videoRegExp.test(value.originalFileName)
-                ? <video controls loop preload="true" height="500">
+            <SortableItemMask id={value._id} setVisible={setVisible} chekMedia={chekMedia} removePhotosItem={removePhotosItem} />
+            {chekMedia
+                ? <video >
                     <source src={backURL + '/' + value.url} />
                 </video>
                 : <img src={backURL + '/' + value.url} />
-            } 
+            }
             <Image className="hidden-item"
                 width={200}
                 style={{ display: 'none' }}

+ 2 - 2
src/components/uploadPhoto/EditTitlePost.js

@@ -9,12 +9,12 @@ import { useEffect, useState } from "react"
 
 export const EditTitlePost = ({ titleSend, setTitleSend }) => {
 
-    const [title, setTitle] = useState(titleSend || 'Enter title')
+    const [title, setTitle] = useState(titleSend || '')
     const [error, setError] = useState(false)
     const [editMode, setEditMode] = useState(false)
 
     useEffect(() => {
-        setTitle(titleSend || 'Enter title')
+        setTitle(titleSend || '')
     }, [titleSend]);
 
     const addValueHandler = () => {

+ 16 - 1
src/helpers/index.js

@@ -14,6 +14,7 @@ export const propsUploadFile = {
     headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {}
 }
 
+
 export const jwtDecode = (token) => {
     try {
         let arrToken = token.split('.')
@@ -53,7 +54,21 @@ const CircularGallerySvg = () =>
     </svg>
 
 export const CircularGalleryIcon = props =>
-    <Icon component={CircularGallerySvg} {...props}/>
+    <Icon component={CircularGallerySvg} {...props} />
+
+const CollectionSvgEmpty = () =>
+    <svg  viewBox="0 0 24 24" ><polygon fill="none" points="20 21 12 13.44 4 21 4 3 20 3 20 21" stroke="currentColor"  strokeWidth="2"></polygon></svg>
+
+
+export const CollectionEmptySvg = props =>
+    <Icon component={CollectionSvgEmpty} {...props} />
+
+const CollectionFillSvg = () =>
+    <svg aria-label="Удалить" color="#262626" fill="#262626" height="24" role="img" viewBox="0 0 24 24" width="24"><path d="M20 22a.999.999 0 01-.687-.273L12 14.815l-7.313 6.912A1 1 0 013 21V3a1 1 0 011-1h16a1 1 0 011 1v18a1 1 0 01-1 1z"></path></svg>
+
+export const CollectionSvgFill = props =>
+    <Icon component={CollectionFillSvg} {...props} />
+
 
 const RRoute = ({ action, component: Component, ...routeProps }) => {
     const WrapperComponent = (componentProps) => {

BIN
src/images/preloader.gif


+ 4 - 2
src/pages/AllPosts.jsx

@@ -3,8 +3,9 @@ import { connect } from 'react-redux';
 import { actionAllPosts, actionRemovePostsFeedAC } from '../actions';
 import { CPosts } from '../components/main/Posts';
 import { Container } from './Content';
+import { CPreloader } from './Preloader';
 
-const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
+const AllPosts = ({ onAllPosts, postsRemove }) => {
     const [checkScroll, setCheckScroll] = useState(true)
 
     useEffect(() => {
@@ -30,9 +31,10 @@ const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
 
     return (
         <Container>
+            <CPreloader promiseName='allPosts' />
             <CPosts />
         </Container>
     )
 }
 
-export const CAllPosts = connect(state => ({ posts: state?.postsFeed?.posts || [] }), { onAllPosts: actionAllPosts, postsRemove: actionRemovePostsFeedAC, })(AllPosts)
+export const CAllPosts = connect(null, { onAllPosts: actionAllPosts, postsRemove: actionRemovePostsFeedAC, })(AllPosts)

+ 42 - 0
src/pages/CollectionPage.jsx

@@ -0,0 +1,42 @@
+import { Divider } from 'antd';
+import Title from 'antd/lib/typography/Title';
+import React, { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { actionFullMyCollectionLoad, actionRemovePostsFeedAC } from '../actions';
+import { CPosts } from '../components/main/Posts';
+import { Container } from './Content';
+import { CPreloader } from './Preloader';
+
+export const CollectionPage = ({ onLoadPosts, postsRemove }) => {
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(() => {
+        if (checkScroll) {
+            onLoadPosts()
+            setCheckScroll(false)
+        }
+    }, [checkScroll])
+
+    useEffect(() => {
+        document.addEventListener('scroll', scrollHandler)
+        return () => {
+            document.removeEventListener('scroll', scrollHandler)
+            postsRemove()
+        }
+    }, [])
+
+    const scrollHandler = (e) => {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+            setCheckScroll(true)
+        }
+    }
+
+    return (
+        <Container>
+            <CPreloader promiseName='onLoadMyCollections' />
+            <Divider><Title level={1}>Collections</Title></Divider>
+            <CPosts /> 
+        </Container>
+    )
+}
+export const CCollectionPage = connect(state => ({ posts: state?.postsFeed?.posts || [] }), { onLoadPosts: actionFullMyCollectionLoad, postsRemove: actionRemovePostsFeedAC })(CollectionPage)

+ 21 - 9
src/pages/EntityEditorPost.jsx

@@ -2,7 +2,7 @@ import { Button, Divider, message } from "antd"
 import Title from "antd/lib/typography/Title"
 import { useEffect, useState } from "react"
 import { connect } from "react-redux"
-import { actionFullSentPost, } from "../actions"
+import { actionAddPostsFeedAC, actionFullSentPost, actionRemovePostsFeedAC, } from "../actions"
 import { EditPhotos } from "../components/uploadPhoto/EditPhotos"
 import { EditDescriptionPost } from "../components/uploadPhoto/EditDescriptionPost"
 import { EditTitlePost } from "../components/uploadPhoto/EditTitlePost"
@@ -13,19 +13,27 @@ import { history } from '../App'
 const ContainEditorPost = ({ children }) =>
     <div className='ContainEditPost ContainerInner'>{children}</div>
 
-const EntityEditorPost = ({ match: { params: { _id } }, myID, entity, status, onSave, findPostOne, clearState }) => {
+const EntityEditorPost = ({ match: { params: { _id } }, myID, entity, status, onSave, updatePost, clearState}) => {
 
     const [photos, setPhotos] = useState(entity?.images || []);
     const [titleSend, setTitleSend] = useState(entity?.title || '')
     const [description, setDescription] = useState(entity?.text || '');
+
+
     useEffect(() => {
-        console.log();
-        if (Array.isArray(entity)) {
-            let newEntity = entity.find(e => e._id === _id)
-            setPhotos(newEntity?.images || [])
-            setTitleSend(newEntity?.title || '')
-            setDescription(newEntity?.text || '')
-        } else if (!Object.keys(entity = {}).length) history.push('/edit/post/new')
+        if (_id !== 'new') {
+            let newEntity
+            if (Array.isArray(entity)) {
+                newEntity = entity.find(e => e._id === _id)
+                setPhotos(newEntity?.images || [])
+                setTitleSend(newEntity?.title || '')
+                setDescription(newEntity?.text || '')
+            } else if (!Object.keys(entity = {}).length) history.push('/edit/post/new')
+            updatePost(newEntity)
+            return () => {
+                clearState()
+            }
+        }
     }, []);
 
 
@@ -33,6 +41,8 @@ const EntityEditorPost = ({ match: { params: { _id } }, myID, entity, status, on
         if (status === "RESOLVED") {
             message.success(`post published, can create a new one`)
             history.push(`/profile/${myID}`)
+        } else if (status === "REJECTED") {
+            message.error('Error')
         }
     }, [status])
 
@@ -59,5 +69,7 @@ export const CEntityEditorPost = connect(state => ({
     status: state?.promise?.sentPost?.status
 }),
     {
+        updatePost: actionAddPostsFeedAC,
         onSave: actionFullSentPost,
+        clearState: actionRemovePostsFeedAC,
     })(EntityEditorPost)

+ 23 - 14
src/pages/Header.jsx

@@ -1,13 +1,14 @@
 import React from 'react'
 import logo from '../logo.svg';
-import { Link } from 'react-router-dom';
+import { Link, NavLink } from 'react-router-dom';
 import { CFieldSearch } from '../components/header/Search';
 import { connect } from 'react-redux';
-import { actionAuthLogout } from '../actions';
+import { actionAuthLogout, actionRemoveMyDataAC } from '../actions';
 import Layout, { Header } from 'antd/lib/layout/layout';
-import {  Col, Menu, Popover, Row } from 'antd';
+import { Col, Menu, Popover, Row } from 'antd';
 import { UserOutlined, CompassOutlined, SettingOutlined, HomeOutlined, ImportOutlined, MessageOutlined, PlusCircleOutlined } from '@ant-design/icons/lib/icons';
 import { UserAvatar } from '../components/header/UserAvatar';
+import { CollectionEmptySvg } from '../helpers';
 
 const UserNav = () =>
     <div className='UserNav'>
@@ -16,39 +17,47 @@ const UserNav = () =>
 
 
 
-const ProfileDropMenu = ({ myID, onLogOut }) =>
+
+
+const ProfileDropMenu = ({ myID, onLogOut, removeMydata }) =>
     <Menu className='dropMenu'>
         <Menu.Item key={'0'}>
             <Link to={`/profile/${myID}`}><UserOutlined /> My Profile</Link>
         </Menu.Item>
         <Menu.Item key={'1'}>
+            <Link to={'/my-collection'}>< CollectionEmptySvg className='dropMenu__icon' />Collection</Link>
+        </Menu.Item>
+        <Menu.Item key={'2'}>
             <Link to={'/my-settings'}><SettingOutlined /> Settings</Link>
         </Menu.Item>
         <Menu.Divider />
-        <Menu.Item key={'2'}>
-            <button onClick={() => onLogOut()}><ImportOutlined /> Log out</button>
+        <Menu.Item key={'3'}>
+            <button onClick={() => {
+                onLogOut()
+                removeMydata()
+            }}><ImportOutlined /> Log out</button>
         </Menu.Item>
     </Menu>
 
-const CProfileDropMenu = connect(null, { onLogOut: actionAuthLogout })(ProfileDropMenu)
+const CProfileDropMenu = connect(null, { onLogOut: actionAuthLogout, removeMydata: actionRemoveMyDataAC })(ProfileDropMenu)
 
 
 const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
-    <Row justify="end" align="middle" className='Header__userNav'>
+    < Row justify="end" align="middle" className='Header__userNav' >
         <Col >
-            <Link to='/'><HomeOutlined /></Link>
+            <NavLink to='/feed'><HomeOutlined /></NavLink>
         </Col>
         <Col >
-            <Link to='/message'><MessageOutlined /></Link>
+            <NavLink to='/message'><MessageOutlined /></NavLink>
         </Col>
         <Col >
-            <Link to='/edit/post/new'><PlusCircleOutlined /></Link>
+            <NavLink to='/edit/post/new'><PlusCircleOutlined /></NavLink>
         </Col>
         <Col >
-            <Link to='/all'><CompassOutlined /></Link>
+            <NavLink to='/all'><CompassOutlined /></NavLink>
         </Col>
         <Col>
-            <Popover placement="bottomRight" content={<CProfileDropMenu myID={_id} />} trigger={['focus', 'hover']}>
+            <Popover placement="bottomRight" content={<CProfileDropMenu myID={_id} />}>
                 <></>
                 <UserAvatar avatar={avatar} login={login} avatarSize={'45px'} />
             </Popover>
@@ -66,7 +75,7 @@ const Logo = () =>
 
 const HeaderComponent = () =>
     <Layout>
-        <Header style={{ position: 'fixed', zIndex: 1, width: '100%' }}>
+        <Header style={{ position: 'fixed', zIndex: 3, width: '100%' }}>
             <Row justify="space-between" align="middle" className='Header__inner'>
                 <Col span={8}>
                     <Logo />

+ 10 - 4
src/pages/MainPostsFeed.jsx

@@ -10,6 +10,7 @@ import PostImage from '../components/main/postsFeed/PostImage'
 import { CPostUserPanel } from '../components/main/postsFeed/PostUserPanel'
 import { Container } from './Content'
 import { CPostTitle } from '../components/main/post/PostTitle'
+import { CPreloader } from './Preloader'
 
 
 export const PostDescription = ({ title, description, date }) =>
@@ -36,13 +37,16 @@ export const Comments = ({ comments = [], _id }) =>
         </Divider>
     </Link>
 
-const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes } }) =>
+const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes, collections } }) =>
     <div className='Post'>
         <Card
-            title={<CPostTitle owner={owner} postId={_id}/>}
+            title={<CPostTitle owner={owner} postId={_id} />}
             cover={<PostImage images={images} />}
         >
-            <CPostUserPanel postId={_id} likes={likes} styleFontSize='1.7em' />
+            <CPostUserPanel postId={_id}
+                likes={likes}
+                collections={collections}
+                styleFontSize='1.7em' />
             <PostDescription title={title} description={text} date={createdAt} />
             <Comments comments={comments} _id={_id} />
         </Card>
@@ -65,6 +69,7 @@ const MainPostsFeed = ({ posts, postsFollowing, clearState, following }) => {
         return () => {
             document.removeEventListener('scroll', scrollHandler)
             clearState()
+
         }
     }, [])
 
@@ -76,6 +81,7 @@ const MainPostsFeed = ({ posts, postsFollowing, clearState, following }) => {
 
     return (
         <Container>
+            <CPreloader promiseName='followingPosts' />
             {Array.isArray(posts) && posts.map(p => <Post key={p._id} postData={p} />)}
         </Container>
     )
@@ -83,7 +89,7 @@ const MainPostsFeed = ({ posts, postsFollowing, clearState, following }) => {
 
 export const CMainPostsFeed = connect(state => ({
     posts: state?.postsFeed?.posts || [],
-    following: state?.myData.following || []
+    following: state?.myData?.following || []
 }), {
     postsFollowing: actionPostsFeed,
     clearState: actionRemovePostsFeedAC,

+ 102 - 73
src/pages/PostPage.jsx

@@ -13,6 +13,7 @@ import { LikeFilled, LikeOutlined } from '@ant-design/icons';
 import { actionLikeComment, actionDelLikeComment, actionSubComment } from '../actions';
 import { CPostTitle } from '../components/main/post/PostTitle';
 import { UserAvatar } from '../components/header/UserAvatar';
+import { CPreloader } from './Preloader';
 
 
 const PostPageTitle = ({ data: { owner }, postId }) =>
@@ -30,81 +31,108 @@ const PostCommentAuthor = ({ owner }) => {
 }
 
 
-const PostComment = ({ myID, subComments, data: { _id, answers, createdAt, likes = [], text, owner }, addLikeComment, removeLikeComment, findSubComment }) => {
-    const [open, setOpen] = useState(false);
 
-    let likeStatus
-    let likeId
-    likes.find(l => {
-        if (l?.owner?._id === myID) {
-            likeStatus = true
-            likeId = l._id
-        } else {
-            likeStatus = false
-        }
-    })
 
-    const changeLike = () => likeStatus ? removeLikeComment(likeId, _id) : addLikeComment(_id)
-
-    const actions = [
-        <span onClick={changeLike}>
-            {likeStatus ? <LikeFilled /> : <LikeOutlined />}
-            <span style={{ paddingLeft: 8, cursor: 'auto' }}>{likes.length ? likes.length : ''}</span>
-        </span>,
-        <span onClick={() => setOpen(true)}>Reply to</span>,
-    ];
-
-    return (
-        <Comment
-            actions={actions}
-            author={<PostCommentAuthor owner={owner} />}
-            avatar={< UserAvatar avatar={owner?.avatar} avatarSize={'35px'} />}
-            content={<p>{text}</p >}
-            datetime={
-                < Tooltip title={moment(new Date(+createdAt)).format('DD-MM-YYYY HH:mm:ss')} >
-                    <span>
-                        {moment(new Date(+createdAt)).startOf('seconds').fromNow()}
-                    </span>
-                </ Tooltip>
-            }
-        >
-            {subComments && subComments['subComments#' + _id]
-                ? subComments['subComments#' + _id].map(s => < CPostSubComment key={s._id} data={s} />)
-                : answers?.length >= 0 && <Divider plain>
-                    <Text type='secodary' onClick={() => findSubComment(_id)}>View answers</Text>
-                </Divider>}
-
-            {open && <CFieldSubCommentSend id={_id} autoFocus={true} value={`@${owner?.nick || owner?.login || ''}, `} setOpen={setOpen} />}
-        </Comment>
-    )
-}
-const CPostComment = connect(state => ({
-    myID: state.auth.payload.sub.id || '',
-    subComments: state?.postsFeed?.subComments
-}), {
-    addLikeComment: actionLikeComment,
-    removeLikeComment: actionDelLikeComment,
-    findSubComment: actionSubComment,
-}
-)(PostComment)
-
-const CPostSubComment = connect(state => ({ comments: state?.postsFeed?.SubComments }), {
-    findSubComment: actionSubComment,
-})(PostComment)
-
-const PostComments = ({ comments }) => {
-    return (
-        <>
-            {
-                comments.map(c => <CPostComment key={c._id} data={c} cId={c._id} />)
-            }
-        </>
-    )
-}
+// const PostComment = ({ myID, subComments, data: { _id, answers, createdAt, likes = [], text, owner }, addLikeComment, removeLikeComment, findSubComment }) => {
+//     const [open, setOpen] = useState(false);
+
+//     let likeStatus
+//     let likeId
+//     likes.find(l => {
+//         if (l?.owner?._id === myID) {
+//             likeStatus = true
+//             likeId = l._id
+//         } else {
+//             likeStatus = false
+//         }
+//     })
+
+//     const changeLike = () => likeStatus ? removeLikeComment(likeId, _id) : addLikeComment(_id)
+
+//     const actions = [
+//         <span onClick={changeLike}>
+//             {likeStatus ? <LikeFilled /> : <LikeOutlined />}
+//             <span style={{ paddingLeft: 8, cursor: 'auto' }}>{likes.length ? likes.length : ''}</span>
+//         </span>,
+//         <span onClick={() => setOpen(true)}>Reply to</span>,
+//     ];
+
+//     return (
+//         <Comment
+//             actions={actions}
+//             author={<PostCommentAuthor owner={owner} />}
+//             avatar={< UserAvatar avatar={owner?.avatar} avatarSize={'35px'} />}
+//             content={<p>{text}</p >}
+//             datetime={
+//                 < Tooltip title={moment(new Date(+createdAt)).format('DD-MM-YYYY HH:mm:ss')} >
+//                     <span>
+//                         {moment(new Date(+createdAt)).startOf('seconds').fromNow()}
+//                     </span>
+//                 </ Tooltip>
+//             }
+//         >
+//             {subComments && subComments['subComments#' + _id]
+//                 ? subComments['subComments#' + _id].map(s => < CPostSubComment key={s._id} data={s} />)
+//                 : answers?.length >= 0 && <Divider plain>
+//                     <Text type='secodary' onClick={() => findSubComment(_id)}>View answers</Text>
+//                 </Divider>}
+
+//             {open && <CFieldSubCommentSend id={_id} autoFocus={true} value={`@${owner?.nick || owner?.login || ''}, `} setOpen={setOpen} />}
+//         </Comment>
+//     )
+// }
+// const CPostComment = connect(state => ({
+//     myID: state.auth.payload.sub.id || '',
+//     subComments: state?.postsFeed?.subComments
+// }), {
+//     addLikeComment: actionLikeComment,
+//     removeLikeComment: actionDelLikeComment,
+//     findSubComment: actionSubComment,
+// }
+// )(PostComment)
+
+// const CPostSubComment = connect(state => ({ comments: state?.postsFeed?.SubComments }), {
+//     findSubComment: actionSubComment,
+// })(PostComment)
+
+const PostComments = ({ comments, findSubComment, parentId }) =>
+    <div>
+        {comments?.length && Object.keys(comments[0]).length > 1
+            ? comments.map(n => {
+                return (
+                    <div className="comment" key={n._id}>
+                        <h3>{n.owner?.login}</h3>
+                        <div>{n.text}</div>
+                        {n.answers && n.answers?.length
+                            ? <> <PostComments comments={n?.answers} parentId={n._id} findSubComment={findSubComment} />
+                            </>
+                            : null}
+                    </div>
+                )
+            })
+            : <h1 onClick={() => findSubComment(parentId)} >{comments.length} мы есть</h1>}
+    </div >
+// n?.answers?.length
+//  <div key={n._id} onClick={() => {
+//                             console.log(parentId || n._id)
+//                             findSubComment(parentId)
+//                         }}
+//                         > vvvvvv Можно кукожитьvvvvvvv</ div >
+
+// const PostComments = ({comments}) => {
+//     return (
+//         <>
+//             {
+//                 comments.map(c => <CPostComment key={c._id} data={c} cId={c._id} />)
+//             }
+//         </>
+//     )
+// }
 
 const CPostComments = connect(state => ({
-    comments: state?.postsFeed?.posts?.comments || []
-}))(PostComments)
+                comments: state?.postsFeed?.posts?.comments || [],
+
+            }), { findSubComment: actionSubComment })(PostComments)
 
 const PostPageDescrption = ({ data: { _id, likes, text, title, createdAt, } }) =>
     <div className='PostOne__description-inner'>
@@ -127,10 +155,11 @@ const CPostPageDescrption = connect(state => ({ data: state?.postsFeed?.posts ||
 
 const PostPage = ({ data: { images } }) =>
     <div className='PostOne'>
+        <CPreloader promiseName='postOne' />
         <div className='PostOne__inner'>
             <div className='PostOne__image'>
-                <PostImage images={images} />
-            </div>
+                    <PostImage images={images} />
+                </div>
             <div className='PostOne__title'>
                 <CPostPageTitle />
             </div>

+ 20 - 0
src/pages/Preloader.jsx

@@ -0,0 +1,20 @@
+import { message, Spin } from 'antd'
+import { connect } from 'react-redux'
+import preloader from '../images/preloader.gif'
+
+const PreloaderImg = () =>
+    <div className='PreloaderImg'>
+        <Spin size="large" />
+    </div>
+
+const Preloader = ({ promiseName, promiseState}) =>
+    <>
+        {promiseState[promiseName]?.status === 'PENDING'
+            ? <PreloaderImg />
+            :
+            promiseState[promiseName]?.status === 'REJECTED'
+                ?
+                message.error(`${promiseState[promiseName]?.error}`) :
+                null}
+    </>
+export const CPreloader = connect(state => ({ promiseState: state.promise }))(Preloader)

+ 2 - 0
src/pages/ProfilePage.jsx

@@ -9,6 +9,7 @@ import Text from 'antd/lib/typography/Text'
 import { Container } from './Content'
 import { CPosts } from '../components/main/Posts'
 import { UserAvatar } from '../components/header/UserAvatar'
+import { CPreloader } from './Preloader'
 
 const ProfileFollowButton = ({ myID, userId, followers, onSubsuscribe, onUnSubsuscribe }) => {
     const followCheck = followers.find(f => f._id === myID && true)
@@ -114,6 +115,7 @@ const ProfilePage = ({ match: { params: { _id } }, getProfileUser, clearDataProf
 
     return (
         <Container>
+            <CPreloader promiseName='userOneDataPosts'/>
             <CProfilePageData setFollowing={setFollowing} setFollowers={setFollowers} />
             {followers && < CModalFollowers statusModal={setFollowers} title={'Followers'} />}
             {following && < CModalFollowing statusModal={setFollowing} title={'Following'} />}

+ 9 - 12
src/pages/SettingsPage.jsx

@@ -13,10 +13,10 @@ const ContainerSettingsPage = ({ children }) =>
     <div className="SettingsPage ContainerInner">{children}</div>
 
 
-const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
+const EditMyDataIput = ({ title, propValue, propHandler, error, setError, setChekEdit }) => {
     const [value, setValue] = useState(propValue);
     const [editMode, setEditMode] = useState(false);
-    const [error, setError] = useState(false);
+
 
     useEffect(() => {
         setValue(propValue)
@@ -24,10 +24,9 @@ const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
 
     const addValueHandler = () => {
         const valid = /^[A-Z][a-z0-9_]{1,15}$/
-        if (!valid.test(value)) {
+        if (valid.test(value)) {
             propHandler(value)
             setEditMode(false)
-            check(false)
         } else {
             setError(true)
         }
@@ -36,6 +35,7 @@ const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
     const onChangeInput = (e) => {
         setValue(e.currentTarget.value)
         setError(false)
+        setChekEdit(false)
     }
 
     return (
@@ -61,9 +61,9 @@ const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
 }
 
 const EditMyData = ({ myData, status, onUpsert }) => {
-
     const [nick, setNick] = useState(myData?.nick || '');
     const [login, setLogin] = useState(myData?.login || '');
+    const [error, setError] = useState(false);
     const [checkEdit, setChekEdit] = useState(true);
     useEffect(() => {
         setNick(myData?.nick || '')
@@ -80,7 +80,7 @@ const EditMyData = ({ myData, status, onUpsert }) => {
                 },
             });
             setChekEdit(true)
-        } else if(status=== 'REJECTED'){
+        } else if (status === 'REJECTED') {
             message.error({
                 content: 'Что-то не так, нужно повторить!',
                 className: 'custom-class',
@@ -93,15 +93,12 @@ const EditMyData = ({ myData, status, onUpsert }) => {
 
     const onSendEdit = () => onUpsert(nick, login)
 
-
-
-
     return (
         <>
-            <EditMyDataIput title='Nick' propValue={nick} propHandler={setNick} check={setChekEdit} />
-            <EditMyDataIput title='Login' propValue={login} propHandler={setLogin} check={setChekEdit} />
+            <EditMyDataIput title='Nick' propValue={nick} propHandler={setNick} setChekEdit={setChekEdit} error={error} setError={setError} />
+            <EditMyDataIput title='Login' error={error} setError={setError} propValue={login} propHandler={setLogin} setChekEdit={setChekEdit} error={error} setError={setError} />
             <Button type='primary'
-                disabled={nick && login ? false : true}
+                disabled={!error ? false : true}
                 className={!checkEdit && '--block'}
                 onClick={onSendEdit}> SendEdit</Button>
         </>

+ 5 - 1
src/redux/reducers/myProfile-reducer.js

@@ -10,7 +10,11 @@ export const myProfileReducer = (state = {}, { type, data }) => {
         },
         'UPDATE-MY-FOLLOWING': () => {
             return { ...state, following: [...data] }
-        }
+        },
+        'UPSERT-COLLECTION': () => ({
+            ...state, collections: data
+        }),
+        'REMOVE-MYDATA': () => ({})
     }
     if (type in types) {
         return types[type]()

+ 23 - 7
src/redux/reducers/postFeed-reducer.js

@@ -3,9 +3,10 @@ import React from 'react'
 export const postsFeedReducer = (state = {}, { type, findId, newResult, userData = {}, count = null }) => {
     const { posts } = state
     const types = {
+        //=== Array.isArray(newResult)
         'ADD-POSTS-FEED': () => ({
             ...state,
-            posts: Array.isArray(posts) === Array.isArray(newResult)
+            posts: Array.isArray(newResult)
                 ? [...posts, ...newResult]
                 : { ...posts, ...newResult },
             count
@@ -23,7 +24,6 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
             userData: {},
             count: 0,
             subComments: {},
-            editPost: {}
         }),
         'ADD-POST-LIKE': () => ({
             ...state,
@@ -42,10 +42,25 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
             ...state, posts: { ...state.posts, comments: [...newResult] }
 
         }),
-        'UPDATE-SUBCOMMENT': () => ({
-            ...state,
-            subComments: { ...state?.subComments, ...{ ['subComments#' + findId]: [...newResult] } }
-        }),
+        'UPDATE-SUBCOMMENT': () => {
+            const recursiya = (commentList, id, nR) => {
+                return commentList.map(c => {
+                    if (c._id === id) {
+                        return { ...c, answers: [...nR] }
+                    } else if (c?.answers?.length) {
+                        return ({
+                            ...c,
+                            answers: recursiya(c.answers, id, nR)
+                        })
+                    } else {
+                        return ({ ...c })
+                    }
+                })
+            }
+            return ({
+                ...state, posts: { ...state.posts, comments: recursiya(posts.comments, findId, newResult) }
+            })
+        },
         'ADD-LIKE-COMMENT': () => ({
             ...state,
             posts: {
@@ -64,7 +79,8 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
         'UPDATE-FOLLOWERS': () => ({
             ...state,
             userData: { ...state.userData, followers: [...newResult] }
-        })
+        }),
+
 
     }
     if (type in types) {

+ 1 - 1
src/redux/reducers/promise-reducer.js

@@ -3,7 +3,7 @@ export function promiseReducer(state = {}, { type, status, payload, error, name
     if (type === 'PROMISE') {
         return {
             ...state,
-            [name]: { status, payload, error }
+            [name]: { status, payload: (status === 'PENDING' && state[name] && state[name].payload) || payload, error }
         }
     } else if (type === 'CLEAR-PROMISE') {
         return {

ファイルの差分が大きいため隠しています
+ 42 - 11
src/redux/saga/index.js