4 Komitmen 4655fad465 ... 1b941bcbab

Pembuat SHA1 Pesan Tanggal
  makstravm 1b941bcbab added text when data empty 2 tahun lalu
  makstravm 2c4237ef5d fix render edit post, and added write comment under posts in postFeed 2 tahun lalu
  makstravm aaa9283309 fix bags, remove modal navigation my profile 2 tahun lalu
  makstravm 346127b5a4 added react- responsive and primitive adaptive 2 tahun lalu

+ 0 - 1
README.md

@@ -1,6 +1,5 @@
 # Getting Started with Create React App
 
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
 
 ## Available Scripts
 

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
         "react-redux": "^7.2.6",
+        "react-responsive": "^9.0.0-beta.6",
         "react-router-dom": "^5.3.0",
         "react-scripts": "5.0.0",
         "react-sortable-hoc": "^2.0.0",

+ 15 - 10
src/App.js

@@ -6,21 +6,26 @@ import { connect, Provider } from 'react-redux';
 import store from './redux/redux-store';
 import { Content, Main } from './pages/Content';
 import { CProfilePage } from './pages/ProfilePage';
-import HeaderComponent from './pages/Header';
+import HeaderComponent from './components/header/Header';
 import { CMainPostsFeed } from './pages/MainPostsFeed';
-import { CRRoute, promiseName } from './helpers';
+import { CRRoute } from './helpers';
 import { CPostPage } from './pages/PostPage';
 import { CAllPosts } from './pages/AllPosts';
 import { CEntityEditorPost } from './pages/EntityEditorPost';
-import { SettingsPage } from './pages/SettingsPage';
+import { CSettingsPage } from './pages/SettingsPage';
 import { Authorization } from './pages/Authorization';
-import { CPreloader } from './pages/Preloader';
 import { CCollectionPage } from './pages/CollectionPage';
+import { useMediaQuery } from 'react-responsive';
+import { FooterComponent } from './components/Footer';
 
 export const history = createHistory()
 
-const AppContent = ({ isToken }) =>
-    <Router history={history}>
+
+const AppContent = ({ isToken }) => {
+    const isTabletDevice = useMediaQuery({
+        query: "(max-width: 786px)"
+    })
+    return <Router history={history}>
         {!isToken
             ?
             <Switch>
@@ -35,19 +40,19 @@ const AppContent = ({ isToken }) =>
                     <Switch>
                         <Route path='/feed' component={CMainPostsFeed} />
                         <Route path='/profile/:_id' component={CProfilePage} />
-                        {/* <Route path='/message' component={Aside} /> */}
-                        <Route path='/edit/post/new' component={CEntityEditorPost} exact />
-                        <Route path='/edit/post/:_id' component={CEntityEditorPost}/>
-                        <Route path='/my-settings' component={SettingsPage} />
+                        <Route path='/edit/post/:_id' component={CEntityEditorPost} />
+                        <Route path='/my-settings' component={CSettingsPage} />
                         <Route path='/all' component={CAllPosts} />
                         <Route path='/my-collection' component={CCollectionPage} />
                         <CRRoute path='/post/:id' component={CPostPage} />
                         <Redirect from='/*' to='/feed' />
                     </Switch>
                 </Main>
+                {isTabletDevice && <FooterComponent />}
             </Content >
         }
     </Router >
+}
 
 const CAppContent = connect(state => ({ isToken: state.auth?.token }))(AppContent)
 store.subscribe(() => console.log(store.getState()))

+ 134 - 106
src/App.scss

@@ -1,53 +1,3 @@
-//     RESET STYLE
-html {
-    box-sizing: border-box;
-}
-
-*,
-*::after,
-*::before {
-    box-sizing: inherit;
-}
-
-ul[class],
-ol[class] {
-    padding: 0;
-}
-
-body,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-p,
-ul[class],
-ol[class],
-li,
-figure,
-figcaption,
-blockquote,
-dl,
-dd {
-    margin: 0;
-}
-
-ul[class] {
-    list-style: none;
-}
-
-img {
-    max-width: 100%;
-    display: block;
-}
-
-input,
-button,
-textarea,
-select {
-    font: inherit;
-}
 $defaultColorW: #fff;
 $defaultColorB: #000;
 
@@ -58,11 +8,12 @@ body {
 video {
     width: 100%;
     margin: 0 auto;
+    height: auto;
     max-height: 500px;
 }
 .Header {
-    display: flex;
     background-color: #ececec;
+    display: flex;
     justify-content: space-between;
     align-items: center;
     .Logo {
@@ -108,6 +59,9 @@ video {
             &.active {
                 svg {
                     fill: $defaultColorB;
+                    polygon {
+                        stroke: $defaultColorB;
+                    }
                 }
             }
         }
@@ -146,38 +100,6 @@ video {
     }
 }
 
-.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;
-    }
-    &__icon {
-        margin-right: 2px;
-        svg {
-            width: 15px;
-            height: 15px;
-        }
-    }
-    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;
@@ -207,17 +129,24 @@ video {
     }
 }
 
-.ant-layout-header {
-    height: 58px;
-    line-height: 58px;
-    padding: 0 20px;
+.ant-layout-header,
+.ant-layout-footer {
+    height: 60px;
     background-color: $defaultColorW;
     box-shadow: 0 0 9px 5px rgba($color: #001529, $alpha: 0.4);
 }
+.ant-layout-header {
+    padding-left: 20px;
+    padding-right: 20px;
+}
+.ant-layout-footer {
+    padding: 10px 20px;
+}
 
 .Main {
     position: relative;
     padding-top: 58px;
+    padding-bottom: 60px;
     &__inner {
         padding-top: 22px;
     }
@@ -236,13 +165,14 @@ video {
         object-fit: contain;
     }
     &__dots.slick-dots {
-        bottom: -14px;
+        z-index: 1;
         li {
             border-radius: 50%;
             background-color: rgba($color: #1890ff, $alpha: 0.5);
             width: 10px;
             height: 10px;
             overflow: hidden;
+            border: 1px solid #000;
             &.slick-active {
                 border-radius: 5px;
                 width: 20px;
@@ -266,7 +196,7 @@ video {
         border: none;
         position: absolute;
         padding: 0;
-        background-color: rgba($color: #FFF, $alpha: 0.7);
+        background-color: rgba($color: #fff, $alpha: 0.7);
         height: 39px;
         width: 39px;
         border-radius: 50%;
@@ -431,7 +361,7 @@ video {
         img,
         video {
             width: 100%;
-            height: 100%;
+            height: auto;
             max-height: 175px;
             margin: 0 auto;
             object-fit: cover;
@@ -462,7 +392,6 @@ video {
     max-width: 1250px;
     padding: 40px 15px 0;
     margin: 0 auto;
-
     &__inner {
         display: grid;
         grid-template-columns: repeat(2, 1fr);
@@ -472,7 +401,6 @@ video {
         align-items: center;
         box-shadow: 0 0 6px 2px rgba($color: $defaultColorB, $alpha: 0.5);
         border-radius: 5px;
-        max-height: 650px;
     }
     &__title {
         grid-area: 1 / 2 / 2 / 3;
@@ -486,13 +414,12 @@ video {
     &__image {
         grid-area: 1 / 1 / 3 / 2;
         width: 60vw;
-        max-width: 700px;
+        overflow: auto;
         height: 100%;
         background-color: rgb(87, 87, 87);
         img {
             max-width: 100%;
-            max-height: 550px;
-            min-height: 450px;
+            max-height: 600px;
             margin: 0 auto;
             object-fit: cover;
         }
@@ -518,13 +445,15 @@ video {
         flex-direction: column;
         justify-content: space-between;
         height: 100%;
-        max-height: 475px;
     }
     &__description-top {
         flex-grow: 1;
-        overflow: auto;
         height: 100%;
     }
+    &__comments {
+        max-height: 350px;
+        overflow: auto;
+    }
     &__comment-edit {
         display: block;
         text-align: right;
@@ -551,11 +480,13 @@ video {
         height: auto;
     }
 }
+
 .PostCommentAuthor {
     color: $defaultColorB;
     font-size: 1.2em;
     font-weight: 500;
 }
+
 .ContainerInner {
     width: 100%;
     background-color: $defaultColorW;
@@ -563,6 +494,7 @@ video {
     box-shadow: 0 0 6px 2px rgba($defaultColorB, 0.5);
     border-radius: 5px;
 }
+
 .ContainEditPost {
     .title {
         width: 100%;
@@ -573,6 +505,7 @@ video {
         white-space: break-spaces;
     }
 }
+
 .EditPhotos {
     &__box {
         margin-bottom: 15px;
@@ -581,6 +514,7 @@ video {
         margin-bottom: 5px;
     }
 }
+
 .SortableList {
     display: grid;
     grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
@@ -609,6 +543,19 @@ video {
         }
     }
 }
+
+.CommentPostFeed {
+    a{
+        font-size: 1.2em;
+    }
+    span {
+        font-size: 0.8em;
+        color: #898989;
+        font-style: italic;
+        padding-left: 15px;
+    }
+}
+
 .SortableItem {
     .Handle {
         position: relative;
@@ -640,6 +587,7 @@ video {
         padding: 10px;
     }
 }
+
 .ant-image-preview-img-wrapper {
     display: flex;
     align-items: center;
@@ -648,18 +596,26 @@ video {
         max-height: 60vh;
     }
 }
+
 .ant-dropdown {
     box-shadow: 0 0 2px 0 $defaultColorB;
 }
-.avatar-uploader > .ant-upload {
-    width: 150px;
-    height: 150px;
-    border-radius: 50%;
-    position: relative;
-    .edit-icon {
-        position: absolute;
-        top: 0;
-        right: 0;
+
+.avatar-uploader {
+    display: flex;
+    justify-content: center;
+    bottom: 10px;
+
+    .ant-upload {
+        width: 150px;
+        height: 150px;
+        border-radius: 50%;
+        position: relative;
+        .edit-icon {
+            position: absolute;
+            top: 0;
+            right: 0;
+        }
     }
 }
 
@@ -703,3 +659,75 @@ video {
         }
     }
 }
+
+.Exit-box__btn {
+    width: 100%;
+    justify-content: flex-end;
+    button {
+        display: block;
+        margin-left: auto;
+    }
+}
+
+@media (max-width: 800px) {
+    .PostOne {
+        padding-top: 30px;
+        &__inner {
+            display: grid;
+            grid-template-columns: 1fr;
+            grid-template-rows: (45px, 1fr, 1fr);
+            max-height: 100%;
+        }
+        &__title {
+            grid-area: 1 / 1 / 2 / 2;
+        }
+        &__image {
+            grid-area: 2 / 1 / 3 / 2;
+            width: 100%;
+            img,
+            video {
+                height: auto;
+            }
+        }
+        &__description {
+            grid-area: 3 / 1 / 4 / 2;
+        }
+        .Profile {
+            &__box {
+                img,
+                video {
+                    max-height: 120px;
+                }
+            }
+        }
+    }
+}
+
+@media (max-width: 560px) {
+    .Logo {
+        img {
+            width: 35vw;
+        }
+    }
+    .Main {
+        padding-left: 10px;
+        padding-right: 5px;
+    }
+    .ant-input-affix-wrapper {
+        width: 45vw;
+        padding: 2px 5px 2px 7px;
+    }
+    .ant-layout-header {
+        padding-left: 10px;
+        padding-right: 10px;
+    }
+    .PostOne {
+        padding-top: 20px;
+        padding-left: 0;
+        padding-right: 0;
+        &__description {
+            padding-left: 10px;
+            padding-right: 10px;
+        }
+    }
+}

+ 20 - 42
src/actions/index.js

@@ -32,15 +32,15 @@ export const actionRegister = (login, password) =>
                                     }
                                 }`, { 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 })
 
+
 export const actionAboutMe = (id) =>
     actionPromise('aboutMe', gql(`query userOned($myID:String!){
                         UserFindOne(query: $myID){
@@ -59,13 +59,12 @@ export const actionUpsertAboutMe = (myData) =>
                         }
                 }`, { user: myData }))
 
+
 //*************** Action Posts Feed ******************//
 
 export const actionGetPostAC = (postData) => ({ type: 'GET-POST', newResult: postData })
-
 export const actionAddPostsFeedAC = (postsData, count) => ({ type: 'ADD-POSTS-FEED', newResult: postsData, count })
 export const actionRemovePostsFeedAC = () => ({ type: 'REMOVE-POSTS-FEED' })
-
 export const actionPostsFeed = () => ({ type: 'POSTS_FEED' })
 
 export const actionPostsMyFollowing = (skip, myFollowing) =>
@@ -76,7 +75,7 @@ export const actionPostsMyFollowing = (skip, myFollowing) =>
                 owner{_id, nick, login, avatar {url}}
                 likes { _id owner {_id}}   
                 images{ url _id originalFileName }
-                comments{_id}
+                comments{_id text  createdAt  owner {_id login nick}}
                 createdAt
         }
     }`, {
@@ -100,11 +99,8 @@ export const actionPostsCount = (_id) =>
 
 
 export const actionProfileDataAC = (postsData, count, userData) => ({ type: 'ADD-PROFILE-DATA', newResult: postsData, count, userData })
-
 export const actionProfilePageData = (id) => ({ type: 'DATA_PROFILE', id })
 
-// export const actionFindPostOne = (_id) => ({ type: 'FIND_POST_ONE', _id })
-
 export const actionProfileData = (_id) =>
     actionPromise('userOneData', gql(` query userOned($id:String!){
                         UserFindOne(query: $id){
@@ -117,9 +113,9 @@ export const actionProfileData = (_id) =>
             } `, { id: JSON.stringify([{ _id }]) }))
 
 export const actionProfilePagePost = (_id, skip) => actionPromise('userOneDataPosts', gql(` query userOned($id:String!){
-                PostFind(query:$id){
-                    _id   images{ url _id originalFileName }
-                }
+                    PostFind(query:$id){
+                        _id   images{ url _id originalFileName }
+                    }
                 }`, {
     id: JSON.stringify([{
         ___owner: _id
@@ -131,8 +127,10 @@ export const actionProfilePagePost = (_id, skip) => actionPromise('userOneDataPo
     }])
 }))
 
+
 //****************---All FIND POSTS---*************************//
 
+
 export const actionAllPosts = () => ({ type: 'ALL_POSTS' })
 
 export const actionGetAllPosts = (skip) =>
@@ -152,7 +150,7 @@ export const actionAllPostsCount = () =>
     actionPromise('userPostsCount', gql(` query userPostsCount($id:String!){
                 PostCount(query:$id)
                 }`, { id: JSON.stringify([{}]) }))
-//    ,
+
 
 //****************---Action FindUsers ---*************************//
 
@@ -181,9 +179,8 @@ export const actionLoadSearchUsers = (value) =>
 
 export const actionAddLikePostAC = (findId, newResult) => ({ type: 'ADD-POST-LIKE', findId, newResult })
 export const actionRemoveLikePostAC = (findId, newResult) => ({ type: 'REMOVE-POST-LIKE', findId, newResult })
-
-
 export const actionLikePost = (postId) => ({ type: 'LIKE_POST', postId })
+export const actionDelLikePost = (likeId, postId) => ({ type: 'DEL_LIKE_POST', likeId, postId })
 
 export const actionAddLikePost = (_id) =>
     actionPromise('likePost', gql(`mutation LikePost($like:LikeInput){
@@ -192,9 +189,6 @@ export const actionAddLikePost = (_id) =>
         }
     }`, { like: { post: { _id } } }))
 
-
-export const actionDelLikePost = (likeId, postId) => ({ type: 'DEL_LIKE_POST', likeId, postId })
-
 export const actionRemoveLikePost = (_id) =>
     actionPromise('removelikePost', gql(`mutation LikeRemove($like:LikeInput){
             LikeDelete(like:$like){
@@ -202,7 +196,6 @@ export const actionRemoveLikePost = (_id) =>
             }
         }`, { like: { _id } }))
 
-
 export const actionMyLikePost = (findId) =>
     actionPromise('myLikes', gql(`query likeFindPost ($id:String!){
         PostFindOne(query:$id){
@@ -215,8 +208,6 @@ export const actionMyLikePost = (findId) =>
 
 
 export const actionUpsertLikeCommentAC = (findId, newResult) => ({ type: 'UPSERT-LIKE-COMMENT', findId, newResult })
-// export const actionRemoveLikeCommentAC = (findId, newResult) => ({ type: 'REMOVE-LIKE-COMMENT', findId, newResult })
-
 export const actionLikeComment = (commentId) => ({ type: 'LIKE_COMMENT', commentId })
 export const actionDelLikeComment = (likeId, commentId) => ({ type: 'DEL_LIKE_COMMENT', likeId, commentId })
 
@@ -228,7 +219,7 @@ export const actionAddLikeComment = (_id) =>
     }`, { like: { comment: { _id } } }))
 
 export const actionRemoveLikeComment = (_id) =>
-    actionPromise('removelikePost', gql(`mutation LikeRemove($like:LikeInput){
+    actionPromise('removelikeComment', gql(`mutation LikeRemove($like:LikeInput){
             LikeDelete(like:$like){_id}
         }`, { like: { _id } }))
 
@@ -245,6 +236,7 @@ export const actionFindLikeComment = (findId) =>
 
 export const actionUpsertCollectionAC = (data) => ({ type: 'UPSERT-COLLECTION', data })
 export const actionHandlerUpsertCollection = (_id, flag) => ({ type: 'HANDLER_UPSERT_COLLECTION', _id, flag })
+export const actionFullMyCollectionLoad = () => ({ type: 'LOAD_COLLECTION' })
 
 export const actionAddPostInCollections = (collectionId, newCollection) =>
     actionPromise('addInCollections', gql(`mutation addInCollections($collection:CollectionInput ){
@@ -260,8 +252,6 @@ export const actionFindMyCollections = (_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){
@@ -275,8 +265,6 @@ export const actionOnLoadMyCollection = (_id, skip) =>
         }])
     }))
 
-//  posts{ images { url _id originalFileName } } 
-
 
 //****************---Action Subscribe ---*************************//
 
@@ -293,6 +281,7 @@ export const actionLoadSubscribe = (myID, myFollowing, userId) =>
             following{_id}
         }
       }`, { user: { _id: myID, following: [...myFollowing || [], { _id: userId }] } }))
+
 export const actionloadUnSubscribe = (myID, myFollowing) =>
     actionPromise('unSubscribe', gql(`mutation followingUn($user:UserInput){
         UserUpsert( user:$user){
@@ -318,15 +307,11 @@ export const actionUpdateFollowers = (_id) =>
 //****************---Action Comments ---*************************//
 
 
-export const actionAddCommentAC = (newResult) => ({ type: 'ADD-COMMENT', newResult })
-
+export const actionAddCommentAC = (findId, newResult) => ({ type: 'ADD-COMMENT', findId, newResult })
 export const actionUpdateSubCommentAC = (findId, newResult) => ({ type: 'UPDATE-SUBCOMMENT', findId, newResult })
-
 export const actionEditCommentAC = (findId, newResult) => ({ type: 'EDIT-COMMENT', findId, newResult })
 export const actionEditComment = (commentId, text) => ({ type: 'COMMENT_EDIT', commentId, text })
-
 export const actionFullAddComment = (postId, text) => ({ type: 'COMMENT_POST', postId, text })
-
 export const actionAddSubComment = (commentId, text) => ({ type: 'ADD_SUB_COMMENT', commentId, text })
 export const actionSubComment = (commentId) => ({ type: 'FIND_SUBCOMMENT', commentId })
 
@@ -379,7 +364,6 @@ export const actionSubAddComment = (commentId, text) =>
         }
     }`, { comment: { answerTo: { _id: commentId }, text } }))
 
-
 export const actionFindCommentText = (findId) =>
     actionPromise('subComments', gql(`query commentFindOne ($id:String!){
         CommentFindOne(query:$id){
@@ -396,12 +380,13 @@ export const actionUpsertEditComment = (commentId, text) =>
         }
     }`, { comment: { _id: commentId, text } }))
 
+
 //****************---Action Udate Avatar ---*************************//
 
+
 export const actionUpdateAvatar = (file) => ({ type: 'UPDATE_AVATAR', file })
 export const actionUpdateMyAvatart = (data) => ({ type: 'ABOUTME-UPDATE-AVATAR', data })
 
-
 export const actionSetAvatar = (file, id) =>
     actionPromise('uploadPhoto', gql(`mutation avaUpsert($ava: UserInput){
                 UserUpsert(user: $ava){
@@ -409,6 +394,7 @@ export const actionSetAvatar = (file, id) =>
                 }
             }`, { ava: { _id: id, avatar: { _id: file._id } } })
     )
+
 export const actionGetAvatar = (id) =>
     actionPromise('uploadPhoto', gql(`query userOned($myID: String!){
         UserFindOne(query: $myID) {
@@ -416,6 +402,7 @@ export const actionGetAvatar = (id) =>
         }
     }`, { myID: JSON.stringify([{ _id: id }]) }))
 
+
 //****************--- Find FOllowing/Follovwrs---*************************//
 
 export const actionFindFollowing = (_id) => ({ type: 'FIND_FOLLOWING', _id })
@@ -456,6 +443,7 @@ export const actionGetFindLiked = (_id) =>
 
 //****************---Create Post ---*************************/
 
+
 export const actionFullSentPost = (images, title, text) => ({ type: 'CREATE_POST', images, text, title })
 
 export const actionUpsertPost = (upSertPostObj) =>
@@ -464,13 +452,3 @@ export const actionUpsertPost = (upSertPostObj) =>
                     _id images{_id url originalFileName}
                 }
             }`, { post: upSertPostObj }))
-
-
-
-
-        //запросить все коменты этого псота с ансвер (айди)
-        // по ансверс.файди найти дочерные коменты и засунуть в массив ансверс как елементы
-        // таким оюразом получтьь дерево
-        // отдать в рекувсивный компонкнт на отрисовку дерева.
-        // каждый лист дерева приконектить к экшонам лайк и добавление дочернего коммета.Там жк=е должен быть известен пост.айди для создания комментов
-

+ 11 - 0
src/components/Footer.jsx

@@ -0,0 +1,11 @@
+import { Footer } from 'antd/lib/layout/layout';
+import React from 'react';
+import { CUserNavIcon } from './header/Header';
+
+export const FooterComponent = () => {
+    return (
+        <Footer style={{ position: 'fixed', zIndex: 3, width: '100%', bottom: 0 }}>
+            <CUserNavIcon />
+        </Footer>
+    )
+};

+ 11 - 10
src/components/FormAuthorization.jsx

@@ -1,14 +1,16 @@
-import React, { useEffect } from 'react'
+import React, { useEffect, useState } from 'react'
 import { connect } from 'react-redux'
 import { Form, Input, Button, Checkbox, message } from 'antd';
 import { UserOutlined, LockOutlined } from '@ant-design/icons';
 import { actionFullLogIn, actionFullRegister } from '../actions';
 
 const FormAuthorization = ({ buttonTitle, onSignIn, loginChek }) => {
-
+    const [loading, setLoading] = useState(false);
     useEffect(() => {
-        if (loginChek.status === "RESOLVED" && loginChek.payload === null) {
-            console.log(loginChek?.payload);
+        if (loginChek?.status === "PENDING") {
+            setLoading(loading => loading = true)
+        }
+        if (loginChek?.status === "RESOLVED" && loginChek?.payload === null) {
             message.error({
                 content: 'Wrong login or password',
                 className: 'custom-class',
@@ -16,8 +18,9 @@ const FormAuthorization = ({ buttonTitle, onSignIn, loginChek }) => {
                     marginTop: '20vh',
                 },
             })
+            setLoading(loading => loading = false)
         }
-    }, [loginChek.status]);
+    }, [loginChek?.status]);
 
 
     const onFinish = ({ login, password, remember }) => {
@@ -71,16 +74,14 @@ const FormAuthorization = ({ buttonTitle, onSignIn, loginChek }) => {
                 <Checkbox>Remember me</Checkbox>
             </Form.Item>
             <Form.Item >
-                <Button type="primary" className="login-form-button" htmlType="submit">
+                <Button type="primary" className="login-form-button" loading={loading} htmlType="submit">
                     {buttonTitle}
                 </Button>
             </Form.Item>
         </Form>
     )
 }
-export const CLoginForm = connect(state => ({ loginChek: state?.promise?.login || {} }), { onSignIn: actionFullLogIn, })(FormAuthorization)
-export const CRegisterForm = connect(state => ({ status: state?.promise?.login }), { 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)

+ 74 - 0
src/components/header/Header.jsx

@@ -0,0 +1,74 @@
+import React from 'react'
+import logo from '../../logo.svg';
+import { Link, NavLink } from 'react-router-dom';
+import { CFieldSearch } from './Search';
+import { connect } from 'react-redux';
+import { Header } from 'antd/lib/layout/layout';
+import { Col, Row } from 'antd';
+import { CompassOutlined, HomeOutlined, MessageOutlined, PlusCircleOutlined } from '@ant-design/icons/lib/icons';
+import { UserAvatar } from './UserAvatar';
+import MediaQuery from "react-responsive";
+import { CollectionEmptySvg } from '../../helpers';
+
+
+const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
+    < Row justify="end" align="middle" className='Header__userNav' >
+        <Col >
+            <NavLink to='/feed'><HomeOutlined /></NavLink>
+        </Col>
+        <Col >
+            <NavLink to='/direct'><MessageOutlined /></NavLink>
+        </Col>
+        <Col >
+            <NavLink to='/edit/post/new'><PlusCircleOutlined /></NavLink>
+        </Col>
+        <Col >
+            <NavLink to='/all'><CompassOutlined /></NavLink>
+        </Col>
+        <Col >
+            <NavLink to='/my-collection'><CollectionEmptySvg /></NavLink>
+        </Col>
+        <MediaQuery minWidth={787}>
+            <Col>
+                <Link to={`/profile/${_id}`}>
+                    <UserAvatar avatar={avatar} login={login} avatarSize={'45px'} />
+                </Link>
+            </Col>
+        </MediaQuery>
+        <MediaQuery maxWidth={786}>
+            <Col>
+                <Link to={`/profile/${_id}`}>
+                    <UserAvatar avatar={avatar} login={login} avatarSize={'45px'} />
+                </Link>
+            </Col>
+        </MediaQuery>
+    </Row >
+
+export const CUserNavIcon = connect(state => ({ userData: state.myData || {} }))(UserNavIcon)
+
+
+const Logo = () =>
+    <Link className='Logo' to='/'>
+        <img src={logo} alt='logo' width='180vw' />
+    </Link>
+
+
+const HeaderComponent = () =>
+    <Header style={{ position: 'fixed', zIndex: 3, width: '100%' }}>
+        <Row justify="space-between" align="middle" className='Header__inner'>
+            <Col flex={2}>
+                <Logo />
+            </Col>
+            <Col flex={1}>
+                <CFieldSearch />
+            </Col>
+            <MediaQuery minWidth={787}>
+                <Col flex={2}>
+                    <CUserNavIcon />
+                </Col>
+            </MediaQuery>
+        </Row>
+    </Header>
+
+
+export default HeaderComponent 

+ 2 - 0
src/components/header/Search.jsx

@@ -5,6 +5,8 @@ import { Link } from 'react-router-dom';
 import {  actionSearchUsers } from '../../actions';
 import { SearchOutlined } from '@ant-design/icons';
 import { UserAvatar } from './UserAvatar';
+
+
 const FindUsersResult = ({ usersRes }) =>
     <div className='Header__search-drop' >
         {

+ 10 - 14
src/components/header/UserAvatar.js

@@ -2,17 +2,13 @@ import Avatar from "antd/lib/avatar/avatar"
 import { backURL } from "../../helpers"
 import noAva from "../../images/noAva.png"
 
-export const UserAvatar = ({ avatarSize, avatar }) => {
-    return (
-        <>
-            <Avatar style={{
-                width: avatarSize,
-                height: avatarSize
-            }}
-                src={avatar && avatar?.url
-                    ? <img src={(backURL + '/' + avatar.url)} alt='avatar' />
-                    : <img src={noAva} alt="avatar"/>
-                } />
-        </>
-    )
-}
+export const UserAvatar = ({ avatarSize, avatar }) =>
+    <Avatar style={{
+        width: avatarSize,
+        height: avatarSize
+    }}
+        src={avatar && avatar?.url
+            ? <img src={(backURL + '/' + avatar.url)} alt='avatar' />
+            : <img src={noAva} alt="avatar" />
+        } />
+

+ 4 - 29
src/components/main/Posts.jsx

@@ -20,42 +20,17 @@ const Posts = ({ posts }) =>
                             </div>
                             : p.images.length === 1
                                 ?
-                                <img src={backURL + '/' + p.images[0]?.url} />
+                                <img src={backURL + '/' + p.images[0]?.url} alt="images post" />
                                 : <div className='Profile__box' >
                                     <CircularGalleryIcon className='Profile__box-icon' style={{ stroke: 'black' }} />
                                     <img src={(backURL + '/' + p?.images[0]?.url)} alt='post Img' />
                                 </div>
-                        : <img src={postNoData} />
+                        : <img src={postNoData} alt='no data' />
                     }
                 </Card>
             </Link>
         </Col>)
         }
     </Row >
-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)
+    
+export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)

+ 119 - 0
src/components/main/post/PostComment.js

@@ -0,0 +1,119 @@
+import React, { useState } from 'react'
+import { Divider, Dropdown, Menu } from 'antd';
+import { connect } from 'react-redux'
+import Text from 'antd/lib/typography/Text';
+import { Comment, Tooltip } from 'antd';
+import moment from 'moment';
+import { Link } from 'react-router-dom';
+import { EditOutlined, LikeFilled, LikeOutlined, MoreOutlined } from '@ant-design/icons';
+import Paragraph from 'antd/lib/typography/Paragraph';
+import { UserAvatar } from '../../header/UserAvatar';
+import { CFieldSubCommentSend, CFieldUpsertCommentSend } from '../postsFeed/FieldComment';
+import { actionDelLikeComment, actionLikeComment, actionSubComment } from '../../../actions';
+
+
+
+const PostCommentAuthor = ({ owner }) =>
+    <>
+        <Link className='PostCommentAuthor' to={`/profile/${owner?._id}`} >
+            {owner?.nick ? owner.nick : owner?.login ? owner.login : 'Null'}
+        </Link>
+    </>
+
+
+const EditMenu = ({ setEditComment }) =>
+    <Menu>
+        <Menu.Item key="1" onClick={() => setEditComment(true)}><EditOutlined /> Edit</Menu.Item>
+    </Menu>
+
+const PostCommentText = ({ myID, commentId, owner, text }) => {
+    const [editComment, setEditComment] = useState(false)
+    return (
+        <>
+            {owner?._id === myID && <Dropdown overlay={<EditMenu setEditComment={setEditComment} />} placement="bottomRight">
+                <span className='PostOne__comment-edit'
+                >
+                    <MoreOutlined />
+                </span >
+            </Dropdown>}
+            {!editComment
+                ? <Dropdown overlay={owner?._id === myID && <EditMenu setEditComment={setEditComment} />} trigger={['contextMenu']}>
+                    <Paragraph ellipsis={{ rows: 2, expandable: true, symbol: 'more' }} >
+                        {text}
+                    </ Paragraph>
+                </Dropdown>
+                : <CFieldUpsertCommentSend value={text} id={commentId} autoFocus={true} setOpen={setEditComment} rows={4} bordered={true} />}
+        </>)
+}
+
+const CPostCommentText = connect(state => ({ myID: state.auth.payload.sub.id || '' }))(PostCommentText)
+
+
+export const PostCommentDate = ({ createdAt }) =>
+    <Tooltip title={moment(new Date(+createdAt)).format('DD-MM-YYYY HH:mm:ss')} >
+        {moment(new Date(+createdAt)).startOf().fromNow()}
+    </ Tooltip>
+
+
+const PostCommentAction = ({ myID, commentId, likes, delLikeComment, addLikeComment }) => {
+    const [open, setOpen] = useState(false);
+    const likeId = likes.find(l => l?.owner?._id === myID)?._id
+
+    const changeLike = () => likeId ? delLikeComment(likeId, commentId) : addLikeComment(commentId)
+
+    return (
+        <>
+            <span onClick={changeLike}>
+                {likeId ? <LikeFilled /> : <LikeOutlined />}
+                <span style={{ paddingLeft: 8, cursor: 'auto' }}>{likes.length ? likes.length : ''}</span>
+            </span>
+            <span onClick={() => setOpen(!open)}>Reply to</span>
+            {open && <CFieldSubCommentSend autoFocus={true} id={commentId} setOpen={setOpen} />}
+        </>
+    )
+}
+
+const CPostCommentAction = connect(state => ({
+    myID: state.auth.payload.sub.id || ''
+}), {
+    addLikeComment: actionLikeComment,
+    delLikeComment: actionDelLikeComment
+})(PostCommentAction)
+
+const PostComments = ({ comments, findSubComment, parentId, }) => {
+    return (<>
+        {comments?.length && Object.keys(comments[0]).length > 1
+            ? comments.map(c => {
+                return (
+                    <Comment
+                        key={c._id}
+                        author={<PostCommentAuthor owner={c.owner} />}
+                        avatar={< UserAvatar avatar={c?.owner?.avatar} avatarSize={'35px'} />}
+                        datetime={<PostCommentDate createdAt={c.createdAt} />}
+                        content={<CPostCommentText text={c.text} commentId={c._id} owner={c.owner} />}
+                        actions={[<CPostCommentAction likes={c.likes} commentId={c._id} />]}
+                    >
+                        {
+                            c.answers && c.answers?.length
+                                ? <>
+                                    <PostComments comments={c?.answers} parentId={c._id} findSubComment={findSubComment} />
+                                </>
+                                : null
+                        }
+                    </Comment>
+                )
+            })
+            :
+            !!comments.length && <Divider plain>
+                <Text type='secodary' onClick={() => findSubComment(parentId)} >
+                    View answers {comments.length}
+                </Text>
+            </Divider>
+        }
+    </>)
+}
+
+export const CPostComments = connect(state => ({
+    comments: state?.postsFeed?.posts?.comments || [],
+
+}), { findSubComment: actionSubComment })(PostComments)

+ 8 - 5
src/components/main/postsFeed/PostImage.jsx

@@ -3,7 +3,7 @@ import { Carousel, Empty } from "antd";
 import React, { createRef } from "react";
 import nodata from '../../../images/nodata.png'
 import { backURL, videoRegExp } from '../../../helpers/index'
-
+import MediaQuery from "react-responsive";
 
 class PostImage extends React.Component {
     constructor(props) {
@@ -46,10 +46,13 @@ class PostImage extends React.Component {
                     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>
+                    <MediaQuery minWidth={768}>
+                        <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>
+                    </MediaQuery>
+
                     {videoRegExp.test(i.originalFileName)
                         ? <video controls loop preload="true" height="500">
                             <source src={backURL + '/' + i.url} />

+ 1 - 1
src/components/main/postsFeed/PostUserPanel.jsx

@@ -1,5 +1,5 @@
 import { HeartFilled, HeartOutlined } from "@ant-design/icons"
-import { Button, Col, Row, Tooltip } from "antd"
+import { Button, Col, Row} from "antd"
 import { useState } from "react"
 import { connect } from "react-redux"
 import { actionDelLikePost, actionHandlerUpsertCollection, actionLikePost, } from "../../../actions"

+ 5 - 5
src/components/uploadPhoto/EditPhotos.js

@@ -60,10 +60,10 @@ const SortableItem = SortableElement(props => {
 });
 
 
-const SortableList = SortableContainer(({ items, ...restProps }) => {
+const SortableList = SortableContainer(({ items=[], ...restProps }) => {
     return (
         <div className='SortableList'>
-            {items.map((item, index) => (
+            {items?.map((item, index) => (
                 <SortableItem
                     key={`item-${item._id}`}
                     index={index}
@@ -76,7 +76,7 @@ const SortableList = SortableContainer(({ items, ...restProps }) => {
 });
 
 
-export function EditPhotos({ photos, setPhotos }) {
+export function EditPhotos({ photos=[], setPhotos }) {
     const [progress, setProgress] = useState(0);
     const [loading, setLoading] = useState(false);
     const handlerChange = async ({ file }) => {
@@ -85,7 +85,7 @@ export function EditPhotos({ photos, setPhotos }) {
             setProgress(file.percent)
         } else if (file.status === 'done') {
             message.success(`${file.name} file uploaded successfully`);
-            setPhotos([...photos, file.response])
+            setPhotos([...photos||[], file.response])
         } else if (file.status === 'error') {
             message.error(`${file.name} file upload failed.`);
         }
@@ -97,7 +97,7 @@ export function EditPhotos({ photos, setPhotos }) {
 
     return (
         <div className="EditPhotos" >
-            {photos.length >= 8 ? null
+            {photos?.length >= 8 ? null
                 : <Dragger {...propsUploadFile}
                     className="EditPhotos__box"
                     multiple={true}

+ 8 - 3
src/helpers/index.js

@@ -6,8 +6,10 @@ import { Route } from 'react-router-dom';
 
 export const backURL = 'http://hipstagram.asmer.fs.a-level.com.ua'
 
+
 export const videoRegExp = (/\.(mp4|mov|avi|wmv|flv|flv|3gp|mpg)$/i)
 
+
 export const propsUploadFile = {
     name: 'photo',
     action: `${backURL}/upload`,
@@ -48,6 +50,7 @@ const getGQL = url =>
 
 export const gql = getGQL(backURL + '/graphql');
 
+
 const CircularGallerySvg = () =>
     <svg aria-label="Кольцевая галерея" color="#ffffff" fill="#ffffff" height="22" role="img" viewBox="0 0 48 48" width="22">
         <path d="M34.8 29.7V11c0-2.9-2.3-5.2-5.2-5.2H11c-2.9 0-5.2 2.3-5.2 5.2v18.7c0 2.9 2.3 5.2 5.2 5.2h18.7c2.8-.1 5.1-2.4 5.1-5.2zM39.2 15v16.1c0 4.5-3.7 8.2-8.2 8.2H14.9c-.6 0-.9.7-.5 1.1 1 1.1 2.4 1.8 4.1 1.8h13.4c5.7 0 10.3-4.6 10.3-10.3V18.5c0-1.6-.7-3.1-1.8-4.1-.5-.4-1.2 0-1.2.6z"></path>
@@ -56,13 +59,14 @@ const CircularGallerySvg = () =>
 export const CircularGalleryIcon = 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>
 
+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>
 
@@ -80,4 +84,5 @@ const RRoute = ({ action, component: Component, ...routeProps }) => {
     return <Route {...routeProps} component={WrapperComponent} />
 }
 
-export const CRRoute = connect(null, { action: match => ({ type: 'ROUTE', match }) })(RRoute)
+export const CRRoute = connect(null, { action: match => ({ type: 'ROUTE', match }) })(RRoute)
+

+ 1 - 1
src/pages/AllPosts.jsx

@@ -24,7 +24,7 @@ const AllPosts = ({ onAllPosts, postsRemove }) => {
     }, [])
 
     const scrollHandler = (e) => {
-        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 300) {
             setCheckScroll(true)
         }
     }

+ 8 - 19
src/pages/CollectionPage.jsx

@@ -1,41 +1,30 @@
 import { Divider } from 'antd';
 import Title from 'antd/lib/typography/Title';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect } 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)
+export const CollectionPage = ({ posts, onLoadPosts, postsRemove }) => {
 
     useEffect(() => {
-        if (checkScroll) {
-            onLoadPosts()
-            setCheckScroll(false)
-        }
-    }, [checkScroll])
-
-    useEffect(() => {
-        document.addEventListener('scroll', scrollHandler)
+        onLoadPosts()
         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 /> 
+            {posts.lenght
+                ? <CPosts />
+                : <Title level={4}>
+                    The collection is empty. Add posts to your collection
+                </Title>}
         </Container>
     )
 }

+ 14 - 12
src/pages/EntityEditorPost.jsx

@@ -13,26 +13,28 @@ import { history } from '../App'
 const ContainEditorPost = ({ children }) =>
     <div className='ContainEditPost ContainerInner'>{children}</div>
 
-const EntityEditorPost = ({ match: { params: { _id } }, myID, entity, status, onSave, updatePost, 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(() => {
         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()
+                let findEntity = entity.find(e => e._id === _id)
+                setPhotos(findEntity?.images)
+                setTitleSend(findEntity?.title)
+                setDescription(findEntity?.text)
+                updatePost(findEntity)
             }
+        } else {
+            setPhotos([])
+            setTitleSend('')
+            setDescription('')
+        }
+        return () => {
+            clearState()
         }
     }, []);
 
@@ -46,7 +48,7 @@ const EntityEditorPost = ({ match: { params: { _id } }, myID, entity, status, on
         }
     }, [status])
 
-    const disabledBtn = photos.length && titleSend && description ? false : true
+    const disabledBtn = photos?.length && titleSend && description ? false : true
     const sentPost = () => onSave(photos, titleSend, description)
 
     return (

+ 0 - 94
src/pages/Header.jsx

@@ -1,94 +0,0 @@
-import React from 'react'
-import logo from '../logo.svg';
-import { Link, NavLink } from 'react-router-dom';
-import { CFieldSearch } from '../components/header/Search';
-import { connect } from 'react-redux';
-import { actionAuthLogout, actionRemoveMyDataAC } from '../actions';
-import Layout, { Header } from 'antd/lib/layout/layout';
-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'>
-        <CUserNavIcon />
-    </div>
-
-
-
-
-
-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={'3'}>
-            <button onClick={() => {
-                onLogOut()
-                removeMydata()
-            }}><ImportOutlined /> Log out</button>
-        </Menu.Item>
-    </Menu>
-
-const CProfileDropMenu = connect(null, { onLogOut: actionAuthLogout, removeMydata: actionRemoveMyDataAC })(ProfileDropMenu)
-
-
-const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
-    < Row justify="end" align="middle" className='Header__userNav' >
-        <Col >
-            <NavLink to='/feed'><HomeOutlined /></NavLink>
-        </Col>
-        <Col >
-            <NavLink to='/message'><MessageOutlined /></NavLink>
-        </Col>
-        <Col >
-            <NavLink to='/edit/post/new'><PlusCircleOutlined /></NavLink>
-        </Col>
-        <Col >
-            <NavLink to='/all'><CompassOutlined /></NavLink>
-        </Col>
-        <Col>
-            <Popover placement="bottomRight" content={<CProfileDropMenu myID={_id} />}>
-                <></>
-                <UserAvatar avatar={avatar} login={login} avatarSize={'45px'} />
-            </Popover>
-        </Col>
-    </Row >
-
-const CUserNavIcon = connect(state => ({ userData: state.myData || {} }))(UserNavIcon)
-
-
-const Logo = () =>
-    <Link className='Logo' to='/'>
-        <img src={logo} alt='logo' width='180vw' />
-    </Link>
-
-
-const HeaderComponent = () =>
-    <Layout>
-        <Header style={{ position: 'fixed', zIndex: 3, width: '100%' }}>
-            <Row justify="space-between" align="middle" className='Header__inner'>
-                <Col span={8}>
-                    <Logo />
-                </Col>
-                <Col span={8}>
-                    <CFieldSearch />
-                </Col>
-                <Col span={8}>
-                    <UserNav />
-                </Col>
-            </Row>
-        </Header>
-    </Layout>
-
-
-export default HeaderComponent 

+ 41 - 8
src/pages/MainPostsFeed.jsx

@@ -11,6 +11,9 @@ import { CPostUserPanel } from '../components/main/postsFeed/PostUserPanel'
 import { Container } from './Content'
 import { CPostTitle } from '../components/main/post/PostTitle'
 import { CPreloader } from './Preloader'
+import { CFieldCommentSend } from '../components/main/postsFeed/FieldComment'
+import { PostCommentDate } from '../components/main/post/PostComment'
+import Title from 'antd/lib/typography/Title'
 
 
 export const PostDescription = ({ title, description, date }) =>
@@ -30,12 +33,37 @@ export const PostDescription = ({ title, description, date }) =>
         </Paragraph>
     </>
 
-export const Comments = ({ comments = [], _id }) =>
-    <Link to={`/post/${_id}`}>
-        <Divider orientation="left">
-            {comments?.length ? `View ${comments.length} comments` : 'No comments'}
-        </Divider>
-    </Link>
+const CommentPostFeed = ({ comment }) =>
+    <div className='CommentPostFeed'>
+        <Link to={`/profile/${comment.owner._id}`}>
+            {comment?.owner?.nick || comment?.owner?.login}
+        </Link>
+        <PostCommentDate createdAt={comment.createdAt} />
+        <Paragraph ellipsis={{ rows: 1, expandable: true, symbol: 'more' }}>
+            {comment?.text}
+        </Paragraph>
+    </div>
+
+export const CommentSPostFeed = ({ comments = [], _id }) =>
+    <>
+        {
+            comments && comments.length
+                ? <>
+                    {(comments.length > 2) && <Link to={`/post/${_id}`}>
+                        <Divider orientation="left">
+                            {comments?.length ? `View ${comments.length} comments` : 'No comments'}
+                        </Divider>
+                    </Link>}
+                    {comments.slice(0, 2).map(c => <CommentPostFeed key={c._id} comment={c} />)}
+                </>
+                : <Link to={`/post/${_id}`}>
+                    <Divider orientation="left">
+                        {comments?.length ? `View ${comments.length} comments` : 'No comments'}
+                    </Divider>
+                </Link>
+        }
+        <CFieldCommentSend id={_id} setOpen={() => { }} />
+    </>
 
 const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes, collections } }) =>
     <div className='Post'>
@@ -48,7 +76,7 @@ const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', com
                 collections={collections}
                 styleFontSize='1.7em' />
             <PostDescription title={title} description={text} date={createdAt} />
-            <Comments comments={comments} _id={_id} />
+            <CommentSPostFeed comments={comments} _id={_id} />
         </Card>
     </div>
 
@@ -82,7 +110,12 @@ const MainPostsFeed = ({ posts, postsFollowing, clearState, following }) => {
     return (
         <Container>
             <CPreloader promiseName='followingPosts' />
-            {Array.isArray(posts) && posts.map(p => <Post key={p._id} postData={p} />)}
+            {Array.isArray(posts) && posts.length
+                ? posts.map(p => <Post key={p._id} postData={p} />)
+                : <Title level={4}>
+                    The tape is empty. Subscribe to users to see them
+                    posts or create your own
+                </Title>}
         </Container>
     )
 }

+ 9 - 118
src/pages/PostPage.jsx

@@ -1,20 +1,15 @@
-import React, { useState } from 'react'
-import { Button, Divider, Dropdown, Menu } from 'antd';
+import React from 'react'
+import { Divider } from 'antd';
 import { connect } from 'react-redux'
 import PostImage from '../components/main/postsFeed/PostImage'
 import { PostDescription } from './MainPostsFeed';
 import Text from 'antd/lib/typography/Text';
-import { CFieldCommentSend, CFieldSubCommentSend, CFieldUpsertCommentSend } from '../components/main/postsFeed/FieldComment';
+import { CFieldCommentSend } from '../components/main/postsFeed/FieldComment';
 import { CPostUserPanel } from '../components/main/postsFeed/PostUserPanel';
-import { Comment, Tooltip } from 'antd';
-import moment from 'moment';
-import { Link } from 'react-router-dom';
-import {  EditOutlined, LikeFilled, LikeOutlined, MoreOutlined } 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';
-import Paragraph from 'antd/lib/typography/Paragraph';
+import { CPostComments } from '../components/main/post/PostComment';
+
 
 
 const PostPageTitle = ({ data: { owner }, postId }) =>
@@ -22,124 +17,20 @@ const PostPageTitle = ({ data: { owner }, postId }) =>
 
 const CPostPageTitle = connect(state => ({ data: state?.postsFeed?.posts || {}, postId: state?.postsFeed?.posts?._id }))(PostPageTitle)
 
-const PostCommentAuthor = ({ owner }) =>
-    <>
-        <Link className='PostCommentAuthor' to={`/profile/${owner?._id}`} >
-            {owner?.nick ? owner.nick : owner?.login ? owner.login : 'Null'}
-        </Link>
-    </>
-
-
-const EditMenu = ({ setEditComment }) =>
-    <Menu>
-        <Menu.Item key="1" onClick={() => setEditComment(true)}><EditOutlined /> Edit</Menu.Item>
-    </Menu>
-
-const PostCommentText = ({ myID, commentId, owner, text }) => {
-    const [editComment, setEditComment] = useState(false)
-    return (
-        <>
-            {owner?._id === myID && <Dropdown overlay={<EditMenu setEditComment={setEditComment} />} placement="bottomRight">
-                <span className='PostOne__comment-edit'
-                >
-                    <MoreOutlined />
-                </span >
-            </Dropdown>}
-            {!editComment
-                ? <Dropdown overlay={owner?._id === myID && <EditMenu setEditComment={setEditComment} />} trigger={['contextMenu']}>
-                    <Paragraph ellipsis={{ rows: 2, expandable: true, symbol: 'more' }} >
-                        {text}
-                    </ Paragraph>
-                </Dropdown>
-                : <CFieldUpsertCommentSend value={text} id={commentId} autoFocus={true} setOpen={setEditComment} rows={4} bordered={true} />}
-        </>)
-}
-
-const CPostCommentText = connect(state => ({ myID: state.auth.payload.sub.id || '' }))(PostCommentText)
-
-
-const PostCommentDate = ({ createdAt }) =>
-    <Tooltip title={moment(new Date(+createdAt)).format('DD-MM-YYYY HH:mm:ss')} >
-        {moment(new Date(+createdAt)).startOf().fromNow()}
-    </ Tooltip>
-
-
-const PostCommentAction = ({ myID, commentId, likes, delLikeComment, addLikeComment }) => {
-    const [open, setOpen] = useState(false);
-    const likeId = likes.find(l => l?.owner?._id === myID)?._id
-
-    const changeLike = () => likeId ? delLikeComment(likeId, commentId) : addLikeComment(commentId)
-
-    return (
-        <>
-            <span onClick={changeLike}>
-                {likeId ? <LikeFilled /> : <LikeOutlined />}
-                <span style={{ paddingLeft: 8, cursor: 'auto' }}>{likes.length ? likes.length : ''}</span>
-            </span>
-            <span onClick={() => setOpen(!open)}>Reply to</span>
-            {open && <CFieldSubCommentSend autoFocus={true} id={commentId} setOpen={setOpen} />}
-        </>
-    )
-}
-
-const CPostCommentAction = connect(state => ({
-    myID: state.auth.payload.sub.id || ''
-}), {
-    addLikeComment: actionLikeComment,
-    delLikeComment: actionDelLikeComment
-})(PostCommentAction)
-
-
-const PostComments = ({ comments, findSubComment, parentId, }) => {
-    return (<>
-        {comments?.length && Object.keys(comments[0]).length > 1
-            ? comments.map(c => {
-                return (
-                    <Comment
-                        key={c._id}
-                        author={<PostCommentAuthor owner={c.owner} />}
-                        avatar={< UserAvatar avatar={c?.owner?.avatar} avatarSize={'35px'} />}
-                        datetime={<PostCommentDate createdAt={c.createdAt} />}
-                        content={<CPostCommentText text={c.text} commentId={c._id} owner={c.owner} />}
-                        actions={[<CPostCommentAction likes={c.likes} commentId={c._id} />]}
-                    >
-                        {
-                            c.answers && c.answers?.length
-                                ? <>
-                                    <PostComments comments={c?.answers} parentId={c._id} findSubComment={findSubComment} />
-                                </>
-                                : null
-                        }
-                    </Comment>
-                )
-            })
-            :
-            !!comments.length && <Divider plain>
-                <Text type='secodary' onClick={() => findSubComment(parentId)} >
-                    View answers {comments.length}
-                </Text>
-            </Divider>
-        }
-    </>)
-}
-
-const CPostComments = connect(state => ({
-    comments: state?.postsFeed?.posts?.comments || [],
-
-}), { findSubComment: actionSubComment })(PostComments)
-
 const PostPageDescrption = ({ data: { _id, likes, text, title, createdAt, } }) =>
     <div className='PostOne__description-inner'>
         <div className='PostOne__description-top'>
             <PostDescription title={title} description={text} date={createdAt} />
             <Divider plain><Text type='secodary'>Comments</Text></Divider>
-            <CPostComments />
+            <div className='PostOne__comments'>
+                <CPostComments />
+            </div>
         </div>
         <div className='PostOne__description-bottom'>
             <Divider />
             <CPostUserPanel likes={likes} postId={_id}
                 styleFontSize='1.3em' />
-            <CFieldCommentSend setOpen={() => { }} /> {/* setOpen - функция заглушка для пропса компонента*/}
+            <CFieldCommentSend id={_id} setOpen={() => { }} /> {/* setOpen - функция заглушка для пропса компонента*/}
         </div>
     </div>
 

+ 3 - 2
src/pages/Preloader.jsx

@@ -4,10 +4,11 @@ import preloader from '../images/preloader.gif'
 
 const PreloaderImg = () =>
     <div className='PreloaderImg'>
-        <Spin size="large" />
+{/* <Spin size="large" /> */}
+        <img src={preloader} alt="preloader" />
     </div>
 
-const Preloader = ({ promiseName, promiseState}) =>
+const Preloader = ({ promiseName, promiseState }) =>
     <>
         {promiseState[promiseName]?.status === 'PENDING'
             ? <PreloaderImg />

+ 1 - 2
src/pages/ProfilePage.jsx

@@ -101,14 +101,13 @@ const ProfilePage = ({ match: { params: { _id } }, getProfileUser, clearDataProf
 
     useEffect(() => {
         if (checkScroll) {
-
             getProfileUser(_id)
             setCheckScroll(false)
         }
     }, [_id, checkScroll])
 
     const scrollHandler = (e) => {
-        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 300) {
             setCheckScroll(true)
         }
     }

+ 15 - 6
src/pages/SettingsPage.jsx

@@ -1,12 +1,12 @@
 import React, { useEffect, useState } from 'react';
 import { Container } from './Content';
 import { CEditAvatar } from '../components/main/profilePage/EditAvatar'
-import { Button, Col, Divider, Input, message, Row } from 'antd'
+import { Button, Col, Divider, Input, message, Row, Space } from 'antd'
 import Title from 'antd/lib/typography/Title';
 import { connect } from 'react-redux';
-import { EditOutlined } from '@ant-design/icons';
+import { EditOutlined, LogoutOutlined } from '@ant-design/icons';
 import Text from 'antd/lib/typography/Text';
-import { actionFullAboutMeUpsert } from '../actions';
+import { actionAuthLogout, actionFullAboutMeUpsert, actionRemoveMyDataAC } from '../actions';
 
 
 const ContainerSettingsPage = ({ children }) =>
@@ -96,7 +96,7 @@ const EditMyData = ({ myData, status, onUpsert }) => {
     return (
         <>
             <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} />
+            <EditMyDataIput title='Login' propValue={login} propHandler={setLogin} setChekEdit={setChekEdit} error={error} setError={setError} />
             <Button type='primary'
                 disabled={!error ? false : true}
                 className={!checkEdit && '--block'}
@@ -107,7 +107,11 @@ const EditMyData = ({ myData, status, onUpsert }) => {
 
 const CEditMyData = connect(state => ({ myData: state?.myData, status: state?.promise?.upsertAboutMe?.status }), { onUpsert: actionFullAboutMeUpsert })(EditMyData)
 
-export const SettingsPage = () => {
+const SettingsPage = ({ onLogOut, removeMydata }) => {
+    const handlerExitBtn = () => {
+        onLogOut()
+        removeMydata()
+    }
     return (
         <Container>
             <ContainerSettingsPage>
@@ -120,7 +124,12 @@ export const SettingsPage = () => {
                         <CEditMyData />
                     </Col>
                 </Row>
+                <Space className='Exit-box__btn'>
+                    <Button onClick={handlerExitBtn}><LogoutOutlined /> Exit</Button>
+                </Space>
             </ContainerSettingsPage>
         </Container>
     )
-};
+}
+
+export const CSettingsPage = connect(null, { onLogOut: actionAuthLogout, removeMydata: actionRemoveMyDataAC })(SettingsPage)

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

@@ -1,4 +1,3 @@
-import React from 'react'
 
 export const myProfileReducer = (state = {}, { type, data }) => {
     const types = {

+ 5 - 2
src/redux/reducers/postFeed-reducer.js

@@ -42,7 +42,10 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
         }),
 
         'ADD-COMMENT': () => ({
-            ...state, posts: { ...state.posts, comments: [...newResult] }
+            ...state,
+            posts: Array.isArray(posts)
+                ? posts.map(p => p._id === findId ? p = { ...p, comments: [...newResult] } : p)
+                : { ...state.posts, comments: [...newResult] }
         }),
 
         'UPDATE-SUBCOMMENT': () => {
@@ -108,7 +111,7 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
                 }
             })
         },
-        
+
         'UPDATE-FOLLOWERS': () => ({
             ...state,
             userData: { ...state.userData, followers: [...newResult] }

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

@@ -1,4 +1,3 @@
-
 export function promiseReducer(state = {}, { type, status, payload, error, name }) {
     if (type === 'PROMISE') {
         return {

File diff ditekan karena terlalu besar
+ 17 - 19
src/redux/saga/index.js


+ 33 - 1
yarn.lock

@@ -3213,6 +3213,11 @@ css-loader@^6.5.1:
     postcss-value-parser "^4.1.0"
     semver "^7.3.5"
 
+css-mediaquery@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
+  integrity sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA=
+
 css-minimizer-webpack-plugin@^3.2.0:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.1.tgz#5afc4507a4ec13dd223f043cda8953ee0bf6ecfa"
@@ -4763,6 +4768,11 @@ human-signals@^2.1.0:
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
+hyphenate-style-name@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
+  integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+
 iconv-lite@0.4.24:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -5922,6 +5932,13 @@ makeerror@1.0.12:
   dependencies:
     tmpl "1.0.5"
 
+matchmediaquery@^0.3.0:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.3.1.tgz#8247edc47e499ebb7c58f62a9ff9ccf5b815c6d7"
+  integrity sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==
+  dependencies:
+    css-mediaquery "^0.1.2"
+
 mdn-data@2.0.14:
   version "2.0.14"
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
@@ -7098,7 +7115,7 @@ prompts@^2.0.1, prompts@^2.4.2:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
-prop-types@^15.5.7:
+prop-types@^15.5.7, prop-types@^15.6.1:
   version "15.8.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7621,6 +7638,16 @@ react-refresh@^0.11.0:
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
   integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
 
+react-responsive@^9.0.0-beta.6:
+  version "9.0.0-beta.6"
+  resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-9.0.0-beta.6.tgz#78dcc9c2af3b176dfa4088dae617a73dba8adbe7"
+  integrity sha512-Flk6UrnpBBByreva6ja/TsbXiXq4BXOlDEKL6Ur+nshUs3CcN5W0BpGe6ClFWrKcORkMZAAYy7A4N4xlMmpgVw==
+  dependencies:
+    hyphenate-style-name "^1.0.0"
+    matchmediaquery "^0.3.0"
+    prop-types "^15.6.1"
+    shallow-equal "^1.2.1"
+
 react-router-dom@^5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
@@ -8173,6 +8200,11 @@ setprototypeof@1.2.0:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
   integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
 
+shallow-equal@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
+  integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
+
 shallowequal@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"