瀏覽代碼

Merge branch 'saga' of makstravm/HipstaGram into master

Maksym Mykulenko 2 年之前
父節點
當前提交
0dd3f83e00
共有 40 個文件被更改,包括 2308 次插入910 次删除
  1. 3 1
      package.json
  2. 16 9
      src/App.js
  3. 243 9
      src/App.scss
  4. 30 0
      src/actions/actionQueries.js
  5. 265 92
      src/actions/index.js
  6. 2 2
      src/components/Authorization.jsx
  7. 31 0
      src/components/Posts.jsx
  8. 24 26
      src/components/header/Search.jsx
  9. 95 5
      src/components/main/Add.js
  10. 11 0
      src/components/main/DateCreated.js
  11. 0 109
      src/components/main/Loo.js
  12. 0 266
      src/components/main/MainPostsFeed.js
  13. 0 160
      src/components/main/Profile.js
  14. 4 0
      src/components/main/new/styles.css
  15. 45 0
      src/components/main/new/styles.scss
  16. 67 0
      src/components/main/postsFeed/FieldComment.jsx
  17. 65 0
      src/components/main/postsFeed/PostImage.jsx
  18. 60 0
      src/components/main/postsFeed/PostUserPanel.jsx
  19. 62 0
      src/components/main/profilePage/ModalFollow.js
  20. 124 0
      src/components/uploadPhoto/index.js
  21. 27 2
      src/helpers/index.js
  22. 二進制
      src/images/profile-post-no.jpeg
  23. 3 3
      src/index.js
  24. 35 0
      src/pages/AllPosts.jsx
  25. 6 4
      src/pages/Content.js
  26. 166 0
      src/pages/EntityEditorPost.jsx
  27. 14 13
      src/components/header/Header.jsx
  28. 98 0
      src/pages/MainPostsFeed.jsx
  29. 142 0
      src/pages/PostPage.jsx
  30. 125 0
      src/pages/ProfilePage.jsx
  31. 0 56
      src/redux/postFeed-reducer.js
  32. 4 4
      src/redux/auth-reducer.js
  33. 3 1
      src/redux/myProfile-reducer.js
  34. 96 0
      src/redux/reducers/postFeed-reducer.js
  35. 0 0
      src/redux/reducers/promise-reducer.js
  36. 5 0
      src/redux/reducers/route-reducer.js
  37. 13 8
      src/redux/redux-store.js
  38. 0 133
      src/redux/redux-thunk.js
  39. 366 0
      src/redux/saga/index.js
  40. 58 7
      yarn.lock

+ 3 - 1
package.json

@@ -8,14 +8,16 @@
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",
         "antd": "^4.18.2",
+        "antd-img-crop": "^4.1.0",
+        "array-move": "^4.0.0",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
         "react-redux": "^7.2.6",
         "react-router-dom": "^5.3.0",
         "react-scripts": "5.0.0",
+        "react-sortable-hoc": "^2.0.0",
         "redux": "^4.1.2",
         "redux-saga": "^1.1.3",
-        "redux-thunk": "^2.4.1",
         "sass": "^1.45.0",
         "web-vitals": "^2.1.2"
     },

+ 16 - 9
src/App.js

@@ -3,13 +3,17 @@ import './App.scss';
 import { Router, Route, Switch, Redirect } from 'react-router-dom';
 import createHistory from "history/createBrowserHistory";
 import { connect, Provider } from 'react-redux';
-import  store  from './redux/redux-store';
+import store from './redux/redux-store';
 import { Authorization } from './components/Authorization';
-import { Content, Main } from './pages/Content';
-import HeaderComponent from './components/header/Header';
-import { CMainPostsFeed } from './components/main/MainPostsFeed';
-import { CProfilePage } from './components/main/Profile';
+import { Container, Content, Main } from './pages/Content';
+import { CProfilePage } from './pages/ProfilePage';
 import { CAdd } from './components/main/Add';
+import HeaderComponent from './pages/Header';
+import { CMainPostsFeed } from './pages/MainPostsFeed';
+import { CRRoute } from './helpers';
+import { CPostPage } from './pages/PostPage';
+import { CAllPosts } from './pages/AllPosts';
+import { CEntityEditorPost } from './pages/EntityEditorPost';
 
 export const history = createHistory()
 
@@ -30,13 +34,16 @@ const AppContent = ({ isToken }) =>
             <Content>
                 <HeaderComponent />
                 <Main>
-                    <Switch>
+                    <Container>
                         <Route path='/' component={CMainPostsFeed} exact />
                         <Route path='/profile/:_id' component={CProfilePage} />
                         <Route path='/message' component={Aside} />
-                        <Route path='/add' component={CAdd} />
-                        <Redirect from='/*' to='/profile/614c8ef4f9fc3a5e42bddb28' />
-                    </Switch>
+                        {/* <Route path='/add' component={CAdd} /> */}
+                        <Route path='/add' component={CEntityEditorPost} />
+                        <CRRoute path='/all' component={CAllPosts} />
+                    </Container>
+                    <CRRoute path='/post/:id' component={CPostPage} />
+                    {/* <Redirect from='/*' to='/' /> */}
                 </Main>
             </Content >
 

+ 243 - 9
src/App.scss

@@ -48,6 +48,13 @@ textarea,
 select {
     font: inherit;
 }
+$defaultColorW: #fff;
+$defaultColorB: #000;
+
+body {
+    background-color: #f9f9f9;
+    color: $defaultColorB;
+}
 
 .Header {
     display: flex;
@@ -72,7 +79,7 @@ select {
         transition: all 0.3s;
         strong {
             padding-left: 15px;
-            color: #000;
+            color: $defaultColorB;
         }
         &:hover {
             background-color: #0057ff1f;
@@ -174,21 +181,24 @@ select {
     height: 58px;
     line-height: 58px;
     padding: 0 20px;
-    background-color: #fff;
+    background-color: $defaultColorW;
     box-shadow: 0 0 9px 5px rgba($color: #001529, $alpha: 0.4);
 }
 
 .Main {
     padding-top: 58px;
     &__inner {
-        padding-top: 15px;
+        padding-top: 22px;
     }
 }
-
+.owner .nick {
+    padding-left: 15px;
+}
 .Post {
     padding: 10px 0;
     position: relative;
     img {
+        width: 100%;
         margin: 0 auto;
         padding: 1px;
     }
@@ -216,6 +226,7 @@ select {
         height: 300px;
         img {
             margin: 0 auto;
+            width: auto;
         }
     }
     &__btn {
@@ -229,7 +240,7 @@ select {
         transition: 0.4s;
         cursor: pointer;
         svg {
-            fill: #fff;
+            fill: $defaultColorW;
             opacity: 0.5;
             width: 50px;
             height: 30px;
@@ -266,7 +277,7 @@ select {
     &__comments {
         a {
             font-size: 1.1em;
-            color: #000;
+            color: $defaultColorB;
             font-weight: 500;
         }
     }
@@ -276,7 +287,21 @@ select {
             box-shadow: none;
         }
     }
+    &__panel-btn {
+        button {
+            cursor: pointer;
+            border: none;
+            padding: 0;
+            margin: 0;
+            background-color: inherit;
+            transition: all 0.3s;
+            &:hover {
+                text-shadow: 1px 1px 3px $defaultColorB;
+            }
+        }
+    }
 }
+
 .Modal {
     .ant-modal-body {
         padding-left: 0;
@@ -285,20 +310,30 @@ select {
     &__inner {
         overflow: auto;
         max-height: 400px;
+        .ant-skeleton-header {
+            padding-left: 20px;
+        }
     }
     li {
         padding-left: 35px;
     }
 }
+
 .Profile {
     width: 100%;
+    padding: 10px;
+    padding-bottom: 30px;
     h1 {
         line-height: 1;
     }
+    &__name {
+        padding-bottom: 10px;
+    }
     &__login {
         color: #8d8d8d;
     }
     &__count {
+        padding-top: 10px;
         strong {
             font-size: 1.2em;
             padding-right: 5px;
@@ -308,8 +343,207 @@ select {
         }
     }
     button {
-        color: #000;
-        // padding: 0;
-        // border: none;
+        color: $defaultColorB;
+    }
+    &__link-message {
+        display: inline-block;
+        background: #1890ff;
+        line-height: 1.5715;
+        box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
+        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+        height: 32px;
+        padding: 4px 15px;
+        font-size: 14px;
+        border-radius: 2px;
+        color: rgba(0, 0, 0, 0.85);
+        border: 1px solid #1890ff;
+        &:hover {
+            color: $defaultColorW;
+            border-color: #40a9ff;
+            background: #40a9ff;
+        }
+    }
+    &__post {
+        padding: 2px;
+        div {
+            padding: 0;
+        }
+        img {
+            width: 100%;
+            height: 100%;
+            max-height: 175px;
+            margin: 0 auto;
+            object-fit: cover;
+        }
+    }
+    &__box {
+        position: relative;
+        &-icon {
+            position: absolute;
+            top: 0;
+            right: 0;
+        }
+    }
+}
+
+.PostOne {
+    max-width: 1250px;
+    padding: 0 15px;
+    margin: 0 auto;
+
+    &__inner {
+        display: grid;
+        grid-template-columns: repeat(2, 1fr);
+        grid-template-rows: 0.1fr 1fr;
+        grid-column-gap: 0px;
+        grid-row-gap: 0px;
+        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;
+        padding: 10px 20px 5px;
+        background-color: $defaultColorW;
+        & .owner {
+            font-size: 1.2em;
+            font-weight: 500;
+        }
+    }
+    &__image {
+        grid-area: 1 / 1 / 3 / 2;
+        width: 60vw;
+        max-width: 700px;
+        height: 100%;
+        background-color: rgb(87, 87, 87);
+        img {
+            max-width: 100%;
+            max-height: 650px;
+            margin: 0 auto;
+            object-fit: cover;
+        }
+        .Post__dots.slick-dots {
+            bottom: 12px;
+        }
+    }
+    &__description {
+        grid-area: 2 / 2 / 3 / 3;
+        background-color: $defaultColorW;
+        padding: 5px 20px;
+        height: 100%;
+        .ant-typography {
+            overflow-wrap: anywhere;
+            margin-bottom: 5px;
+        }
+        .ant-divider {
+            margin: 0;
+        }
+    }
+    &__description-inner {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        height: 100%;
+        max-height: 565px;
+    }
+    &__description-top {
+        flex-grow: 1;
+        overflow: auto;
+        height: 100%;
+    }
+    .ant-empty-image {
+        height: auto;
+    }
+}
+.PostCommentAuthor {
+    color: $defaultColorB;
+    font-size: 1.2em;
+    font-weight: 500;
+}
+
+.ContainErditorPost {
+    width: 100%;
+    background-color: $defaultColorW;
+    padding: 15px 25px;
+    box-shadow: 0 0 6px 2px rgba($defaultColorB, 0.5);
+    border-radius: 5px;
+    .title {
+        width: 100%;
+        text-align: center;
+        display: inline-block;
+    }
+    .description {
+        white-space: pre;
+    }
+}
+.EditPhotos {
+    &__box {
+        margin-bottom: 15px;
+    }
+    .ant-upload-drag-icon {
+        margin-bottom: 5px;
+    }
+}
+.SortableList {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
+    grid-gap: 10px;
+}
+
+.SortableItemMask {
+    position: absolute;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    top: 0px;
+    left: 0px;
+    right: 0px;
+    border-radius: 4px;
+    background-color: rgba($defaultColorB, 0.5);
+    opacity: 0;
+    transition: 0.3s;
+    button {
+        svg {
+            width: 15px;
+            height: 15px;
+            fill: $defaultColorW;
+        }
+    }
+    .hiden-item,
+    .ant-image-mask {
+        position: absolute;
+        top: 0;
+        left: 0;
+        clip: rect(0 0 0);
+        pointer-events: none;
+        padding: 10px;
+    }
+}
+.SortableItem {
+    .Handle {
+        position: relative;
+        max-width: 130px;
+        border-radius: 4px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-shadow: 0 0 6px 2px rgba($defaultColorB, 0.3);
+        img {
+            width: 100%;
+            object-fit: cover;
+            max-height: 150px;
+        }
+        &:hover .SortableItemMask {
+            opacity: 1;
+        }
+    }
+}
+.ant-image-preview-img-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    img {
+        max-height: 60vh;
     }
 }

+ 30 - 0
src/actions/actionQueries.js

@@ -0,0 +1,30 @@
+export const queries = {
+    "/post/:id": match => ({
+        name: 'postOne',
+        query: `query post($id:String!) {
+                    PostFindOne(query:$id) {
+                        _id createdAt title text 
+                        images{_id url text}
+                        comments {
+                            _id createdAt text 
+                            likes { _id owner {_id}}   
+                            owner {_id login nick
+                                    avatar {url}
+                                }
+                            answers{
+                                _id 
+                                }
+                               answerTo{_id} 
+                            }
+                        likes{ _id
+                            owner{_id}
+                        }
+                        owner {_id login nick
+                            avatar {url}
+                            }
+                        }
+                    }`,
+        variables: { id: JSON.stringify([{ _id: match.params.id }]) }
+    }),
+}
+

+ 265 - 92
src/actions/index.js

@@ -1,32 +1,29 @@
 import { gql } from "../helpers";
-import { actionPromise } from "../redux/redux-thunk";
 
 
+//*************** ACTIONS PROMISE ******************//
+
 
 export const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
 export const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
 export const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
 
-export const actionAuthLogin = (token, remember) => ({ type: 'AUTH_LOGIN', token, remember })
-export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
+export const actionPromise = (name, promise) => ({ type: 'PROMISE_START', name, promise })
 
-export const actionAboutMeAC = (data) => ({ type: 'ABOUTME-DATA-ADD', data })
-export const actionUpdateMyAvatart = (data) => ({ type: 'ABOUTME-UPDATE-AVATAR', data })
-export const actionAddPostsFeedAC = (count, newResult, userData) => ({ type: 'ADD-POSTS-FEED', newResult, userData, count })
-export const actionRemovePostsFeedAC = () => ({ type: 'REMOVE-POSTS-FEED' })
 
-export const actionAddLikePostAC = (postId, newResult) => ({ type: 'ADD-POST-LIKE', postId, newResult })
-export const actionRemoveLikePostAC = (postId, newResult) => ({ type: 'REMOVE-POST-LIKE', postId, newResult })
-export const actionAddCommentAC = (postId, newResult) => ({ type: 'ADD-COMMENT', postId, newResult })
+//*************** ACTIONS AUTHORIZATION ******************//
 
-export const actionUpdateFollowersAC = (newResult) => ({ type: 'UPDATE-FOLLOWERS', newResult })
 
-//****************---Action Authirization ---*************************//
+export const actionAuthLogin = (token, remember) => ({ type: 'AUTH-LOGIN', token, remember })
+export const actionAuthLogout = () => ({ type: 'AUTH-LOGOUT' })
 
-export const actionLogin = (login, password) =>
+export const actionLogIn = (login, password) =>
     actionPromise('login', gql(`query login($login:String!, $password:String!){
-            login(login:$login, password:$password)
-        }`, { login, password }))
+                login(login:$login, password:$password)
+            }`, { login, password }))
+
+export const actionFullLogIn = (login, password, remember) => ({ type: 'FULL_LOGIN', login, password, remember })
+export const actionFullRegister = (login, password, remember) => ({ type: 'FULL_REGISTER', login, password, remember })
 
 export const actionRegister = (login, password) =>
     actionPromise('register', gql(`mutation rega ($login:String!, $password:String!){
@@ -35,6 +32,13 @@ 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 actionAboutMe = (id) =>
     actionPromise('aboutMe', gql(`query userOned($myID:String!){
                         UserFindOne(query: $myID){
@@ -45,21 +49,30 @@ export const actionAboutMe = (id) =>
                 }`, { myID: JSON.stringify([{ ___owner: id }]) }))
 
 
+//*************** 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 actionMyFolowingPosts = (skip, myFollowing) =>
+export const actionPostsFeed = () => ({ type: 'POSTS_FEED' })
+
+export const actionPostsMyFollowing = (skip, myFollowing) =>
     actionPromise('followingPosts',
         gql(`query allposts($query: String!){
-        PostFind(query:$query){
-            _id, text, title
-            owner{_id, nick, login, avatar {url}}
-            likes { _id owner {_id}}   
-            images{url _id}
-            comments{_id text owner{_id nick login} likes{_id}}
-            createdAt
+            PostFind(query:$query){
+                _id, text, title
+                owner{_id, nick, login, avatar {url}}
+                likes { _id owner {_id}}   
+                images{url _id}
+                comments{_id}
+                createdAt
         }
     }`, {
-            query: JSON.stringify([{ ___owner: { $in: myFollowing } },
+            query: JSON.stringify([{
+                ___owner: { $in: myFollowing }
+            },
             {
                 sort: [{ _id: -1 }],
                 skip: [skip || 0],
@@ -67,13 +80,75 @@ export const actionMyFolowingPosts = (skip, myFollowing) =>
             }])
         }))
 
+export const actionPostsCount = (_id) =>
+    actionPromise('userPostsCount', gql(` query userPostsCount($id:String!){
+                PostCount(query:$id)
+                }`, { id: JSON.stringify([{ ___owner: { $in: _id } }]) }))
+
+
+//*************** Action Posts Profile ******************//
+
 
-// 
+export const actionProfileDataAC = (postsData, count, userData) => ({ type: 'ADD-PROFILE-DATA', newResult: postsData, count, userData })
+
+export const actionProfilePageData = (id) => ({ type: 'DATA_PROFILE', id })
+
+export const actionProfileData = (_id) =>
+    actionPromise('userOneData', gql(` query userOned($id:String!){
+                        UserFindOne(query: $id){
+                            _id  login nick
+                            avatar { _id url }     
+                            createdAt
+                            followers {_id }
+                            following {_id }
+                }
+            } `, { id: JSON.stringify([{ _id }]) }))
+
+export const actionProfilePagePost = (_id, skip) => actionPromise('userOneDataPosts', gql(` query userOned($id:String!){
+                PostFind(query:$id){
+                    _id   images{url _id}
+                }
+                }`, {
+    id: JSON.stringify([{
+        ___owner: _id
+    },
+    {
+        sort: [{ _id: -1 }],
+        skip: [skip || 0],
+        limit: [24]
+    }])
+}))
+
+
+//****************---All FIND POSTS---*************************//
+
+export const actionAllPosts = () => ({ type: 'ALL_POSTS' })
+
+export const actionGetAllPosts = (skip) =>
+    actionPromise('allPosts', gql(` query allPosts($id:String!){
+                PostFind(query:$id){
+                    _id   images{url _id}
+                }
+            }`, {
+        id: JSON.stringify([{}, {
+            sort: [{ _id: -1 }],
+            skip: [skip || 0],
+            limit: [36]
+        }])
+    }))
 
+export const actionAllPostsCount = () =>
+    actionPromise('userPostsCount', gql(` query userPostsCount($id:String!){
+                PostCount(query:$id)
+                }`, { id: JSON.stringify([{}]) }))
+//    ,
 
 //****************---Action FindUsers ---*************************//
 
-export const actionFindUsers = (value) =>
+
+export const actionSearchUsers = (value) => ({ type: 'SEARCH_USERS', value })
+
+export const actionLoadSearchUsers = (value) =>
     actionPromise('findUsersAll', gql(`query findUsersAll($query:String!) {
                                 UserFind(query: $query) {
                                     _id login nick 
@@ -89,14 +164,15 @@ export const actionFindUsers = (value) =>
         ])
     }))
 
-//****************---Action Like ---*************************//
 
-export const actionRemoveLikePost = (_id) =>
-    actionPromise('removelikePost', gql(`mutation LikeRemove($like:LikeInput){
-        LikeDelete(like:$like){
-            _id
-        }
-    }`, { like: { _id } }))
+//****************---Action Like Post ---*************************//
+
+
+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 actionAddLikePost = (_id) =>
     actionPromise('likePost', gql(`mutation LikePost($like:LikeInput){
@@ -105,66 +181,75 @@ export const actionAddLikePost = (_id) =>
         }
     }`, { like: { post: { _id } } }))
 
-export const actionMyLikePost = (postId) =>
+
+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){
+                _id
+            }
+        }`, { like: { _id } }))
+
+
+export const actionMyLikePost = (findId) =>
     actionPromise('myLikes', gql(`query likeFindPost ($id:String!){
         PostFindOne(query:$id){
-        likes { _id owner {_id}} 
+        likes { _id owner {_id}}
         }
-    }`, { id: JSON.stringify([{ _id: postId }]) }))
+    }`, { id: JSON.stringify([{ _id: findId }]) }))
 
 
-//****************---Action Comment ---*************************//
+//****************---Action Like Comment ---*************************//
 
-export const actionAddComment = (postId, text) =>
-    actionPromise('addcomment', gql(`mutation addcomment($comment: CommentInput ){
-        CommentUpsert(comment:$comment){
-            _id text
+export const actionAddLikeCommentAC = (findId, newResult) => ({ type: 'ADD-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 })
+
+export const actionAddLikeComment = (_id) =>
+    actionPromise('likePost', gql(`mutation LikePost($like:LikeInput){
+        LikeUpsert(like:$like){
+            _id
         }
-    }`, { comment: { post: { _id: postId }, text } }))
+    }`, { like: { comment: { _id } } }))
 
-export const actionFindComment = (postId) =>
-    actionPromise('findCommentPost', gql(`query commentFindPost ($id:String!){
-        PostFindOne(query:$id){
-         comments{_id text owner{_id nick login} likes{_id}}
+export const actionRemoveLikeComment = (_id) =>
+    actionPromise('removelikePost', gql(`mutation LikeRemove($like:LikeInput){
+            LikeDelete(like:$like){_id}
+        }`, { like: { _id } }))
+
+export const actionFindLikeComment = (findId) =>
+    actionPromise('findLikeComment', gql(`query findLikeComment ($id:String!){
+        CommentFindOne(query:$id){
+        likes { _id owner {_id}}
         }
-    }`, { id: JSON.stringify([{ _id: postId }]) }))
+    }`, { id: JSON.stringify([{ _id: findId }]) }))
 
-//****************---Action ProfileData ---*************************//
 
-export const actionProfilePageData = (_id) =>
-    actionPromise('userOneData', gql(` query userOned($id:String!){
-                        UserFindOne(query: $id){
-                            _id  login nick
-                            avatar { _id url }     
-                            createdAt
-                            followers {_id nick login}
-                            following {_id nick login}
-                }
-            } `, { id: JSON.stringify([{ _id }]) }))
+//****************---Action Subscribe ---*************************//
 
-export const actionProfilePagePost = (_id, skip) =>
-    actionPromise('userOneDataPosts', gql(` query userOned($id:String!){
-                PostFind(query:$id){
-                    _id   images{url _id}
-                }
-                }`, {
-        id: JSON.stringify([{
-            ___owner: _id
-        },
-        {
-            sort: [{ _id: -1 }],
-            skip: [skip || 0],
-            limit: [10]
-        }])
-    }))
 
-export const actionProfilePostCount = (_id) =>
-    actionPromise('userPostsCount', gql(` query userPostsCount($id:String!){
-                PostCount(query:$id)
-                }`, { id: JSON.stringify([{ ___owner: { $in: _id } }]) }))
+export const actionUpdateMyFollowingAC = (data) => ({ type: 'UPDATE-MY-FOLLOWING', data })
+export const actionUpdateFollowersAC = (newResult) => ({ type: 'UPDATE-FOLLOWERS', newResult })
+
+export const actionSubscribe = (userId) => ({ type: 'SUBSCRIBE', userId })
+export const actionUnSubscribe = (userId) => ({ type: 'UN_SUBSCRIBE', userId })
+
+export const actionLoadSubscribe = (myID, myFollowing, userId) =>
+    actionPromise('subscribe', gql(`mutation following($user:UserInput){
+        UserUpsert( user:$user){
+            following{_id}
+        }
+      }`, { user: { _id: myID, following: [...myFollowing || [], { _id: userId }] } }))
+export const actionloadUnSubscribe = (myID, myFollowing) =>
+    actionPromise('unSubscribe', gql(`mutation followingUn($user:UserInput){
+        UserUpsert( user:$user){
+            following{_id}
+        }
+      }`, { user: { _id: myID, following: [...myFollowing] } }))
 
-//****************---Action ProfileData ---*************************//
-//  
 export const actionUpdateMyFollowing = (_id) =>
     actionPromise('upDateFollowing', gql(` query followers($id:String!){
         UserFindOne(query: $id){
@@ -172,7 +257,6 @@ export const actionUpdateMyFollowing = (_id) =>
         }
     }`, { id: JSON.stringify([{ _id }]) }))
 
-
 export const actionUpdateFollowers = (_id) =>
     actionPromise('upDateFollowers', gql(` query followers($id:String!){
         UserFindOne(query: $id){
@@ -180,23 +264,64 @@ export const actionUpdateFollowers = (_id) =>
         }
     }`, { id: JSON.stringify([{ _id }]) }))
 
-export const actionSubscribe = (myID, myFollowing, userId) =>
-    actionPromise('subscribe', gql(`mutation following($user:UserInput){
-        UserUpsert( user:$user){
-            following{_id}
+
+//****************---Action Comments ---*************************//
+
+
+export const actionAddCommentAC = (newResult) => ({ type: 'ADD-COMMENT', newResult })
+export const actionUpdateSubCommentAC = (findId, newResult) => ({ type: 'UPDATE-SUBCOMMENT', findId, newResult })
+
+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 })
+
+export const actionAddComment = (postId, text) =>
+    actionPromise('addcomment', gql(`mutation addcomment($comment: CommentInput ){
+        CommentUpsert(comment:$comment){
+            _id text
         }
-      }`, { user: { _id: myID, following: [...myFollowing || [], { _id: userId }] } }))
+    }`, { comment: { post: { _id: postId }, text } }))
 
-export const actionUnSubscribe = (myID, myFollowing) =>
-    actionPromise('unSubscribe', gql(`mutation followingUn($user:UserInput){
-        UserUpsert( user:$user){
-            following{_id}
+export const actionFindComment = (findId) =>
+    actionPromise('findCommentPost', gql(`query commentFindPost ($id:String!){
+        PostFindOne(query:$id){
+            comments {
+                _id text owner{
+                    _id nick login
+                     avatar{_id url}
+                    } 
+                    likes{_id}
+                }
         }
-      }`, { user: { _id: myID, following: [...myFollowing] } }))
+    }`, { id: JSON.stringify([{ _id: findId }]) }))
+
+export const actionFindSubComment = (findId) =>
+    actionPromise('subComments#' + findId, 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 }
+            }
+        } 
+    }`, { id: JSON.stringify([{ _id: findId }]) }))
+
+export const actionSubAddComment = (commentId, text, _id) =>
+    actionPromise('addSubcomment', gql(`mutation addSubcomment($comment: CommentInput ){
+        CommentUpsert(comment:$comment){
+            _id text
+        }
+    }`, { comment: { answerTo: { _id: commentId }, text } }))
 
 
+//****************---Action Udate Avatar ---*************************//
 
-//****************---Action Upload Images ---*************************//
+export const actionUpdateAvatar = (file) => ({ type: 'UPDATE_AVATAR', file })
+export const actionUpdateMyAvatart = (data) => ({ type: 'ABOUTME-UPDATE-AVATAR', data })
 
 
 export const actionSetAvatar = (file, id) =>
@@ -204,7 +329,7 @@ export const actionSetAvatar = (file, id) =>
                 UserUpsert(user: $ava){
                     _id avatar {_id}
                 }
-              }`, { ava: { _id: id, avatar: { _id: file._id } } })
+            }`, { ava: { _id: id, avatar: { _id: file._id } } })
     )
 export const actionGetAvatar = (id) =>
     actionPromise('uploadPhoto', gql(`query userOned($myID: String!){
@@ -212,3 +337,51 @@ export const actionGetAvatar = (id) =>
                             avatar { _id url }
         }
     }`, { myID: JSON.stringify([{ ___owner: id }]) }))
+
+
+//****************--- Find FOllowing/Follovwrs---*************************//
+
+export const actionFindFollowing = (_id) => ({ type: 'FIND_FOLLOWING', _id })
+export const actionFindFollowers = (_id) => ({ type: 'FIND_FOLLOWERS', _id })
+export const actionFindLiked = (_id) => ({ type: 'FIND_LIKED', _id })
+
+export const actionGetFindFollowing = (_id) =>
+    actionPromise('findFollow', gql(` query findFollowing($id:String!){
+                        UserFindOne(query: $id){
+                            following {
+                                _id nick login
+                                avatar { _id url }
+                            }
+                }
+            } `, { id: JSON.stringify([{ _id }]) }))
+
+export const actionGetFindFollowers = (_id) =>
+    actionPromise('findFollow', gql(` query findFollowers($id:String!){
+                        UserFindOne(query: $id){
+                            followers {
+                                _id  nick login
+                                avatar { _id url }
+                            }
+                }
+            } `, { id: JSON.stringify([{ _id }]) }))
+
+export const actionGetFindLiked = (_id) =>
+    actionPromise('findLiked', gql(` query LikeFindPost($id:String!) {
+                                        LikeFind(query:$id){
+                                          owner { _id nick login
+                                                avatar{_id url}
+                                    }
+                }
+            } `, { id: JSON.stringify([{ "post._id": _id }]) }))
+
+
+//****************---Create Post ---*************************/
+
+export const actionsentPost = (_id = '', photos, text, title) => ({ type: 'CREATE_POST', photos, text, title })
+
+export const actionSentPost = (photos, title, text, id = "undefined") =>
+    actionPromise('sentPost', gql(`mutation sentPost($post: PostInput){
+              PostUpsert(post: $post){
+                    _id images{_id url}
+                }
+            }`, { post: { text, title, images: { _id: photos._id } } }))

+ 2 - 2
src/components/Authorization.jsx

@@ -5,7 +5,7 @@ import { NavLink} from 'react-router-dom'
 
 import { Form, Input, Button, Row, Col, Card, Divider, Checkbox } from 'antd';
 import { UserOutlined, LockOutlined } from '@ant-design/icons';
-import { actionFullLogin, actionFullRegister } from '../redux/redux-thunk';
+import { actionFullLogIn, actionFullRegister } from '../actions';
 
 const FormInput = ({ buttonTitle, onSignIn }) => {
     const onFinish = ({ login, password, remember }) => {
@@ -65,7 +65,7 @@ const FormInput = ({ buttonTitle, onSignIn }) => {
         </Form>
     )
 }
-const CLoginForm = connect(null, { onSignIn: actionFullLogin})(FormInput)
+const CLoginForm = connect(null, { onSignIn: actionFullLogIn})(FormInput)
 const CRegisterForm = connect(null, { onSignIn: actionFullRegister})(FormInput)
 
 export const Authorization = ({ match: { params: { _id } } }) => {

+ 31 - 0
src/components/Posts.jsx

@@ -0,0 +1,31 @@
+import { connect } from "react-redux"
+import { Card, Col, Row } from 'antd'
+import postNoData from '../images/profile-post-no.jpeg'
+import { backURL, CircularGalleryIcon } from '../helpers'
+import { Link } from "react-router-dom"
+
+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?.images && p?.images[0] && p.images[0]?.url
+                        ?
+                        p.images.length === 1
+                            ?
+                            < img src={(backURL + '/' + p?.images[0].url)} alt='post Img' />
+                            :
+                            <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} />}
+                </Card>
+            </Link>
+        </Col>)
+        }
+    </Row >
+
+
+export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)

+ 24 - 26
src/components/header/Search.jsx

@@ -2,12 +2,11 @@ import { Empty, Input, Popover } from 'antd'
 import React from 'react'
 import { connect } from 'react-redux';
 import { Link } from 'react-router-dom';
-import { actionFindUsers } from '../../actions';
-import { UserAvatar } from './Header';
-
-const { Search } = Input;
-const FindUsersResult = ({ usersRes }) => {
-    return <div className='Header__search-drop' >
+import { actionFindUsers, actionSearchUsers } from '../../actions';
+import { UserAvatar } from '../../pages/Header';
+import { SearchOutlined } from '@ant-design/icons';
+const FindUsersResult = ({ usersRes }) =>
+    <div className='Header__search-drop' >
         {
             usersRes.length === 0 ?
                 <Empty /> :
@@ -22,25 +21,24 @@ const FindUsersResult = ({ usersRes }) => {
                 })
         }
     </div >
-}
 
-export const FieldSearch = ({ usersRes, findUsers }) => {
-    return (
-        <>
-            <Popover placement="bottom"
-                content={<FindUsersResult usersRes={usersRes} />}
-                destroyTooltipOnHide={true}
-                trigger="focus">
-                <></>
-                <Search className='Header__search'
-                    onSearch={value => findUsers(value)}
-                    placeholder="Search users"
-                    allowClear
-                    enterButton="Search"
-                    enterButton />
-            </Popover>
-        </>
-    )
-}
 
-export const CFieldSearch = connect(state => ({ usersRes: state.promise?.findUsersAll?.payload || [] }), { findUsers: actionFindUsers })(FieldSearch) 
+export const FieldSearch = ({ usersRes, findUsers }) =>
+    <>
+        <Popover placement="bottom"
+            content={<FindUsersResult usersRes={usersRes} />}
+            destroyTooltipOnHide={true}
+            trigger="focus">
+            <></>
+            <Input
+                placeholder="Search users"
+                allowClear
+                prefix={<SearchOutlined style={{ color: '#c9c9c9' }} />}
+                onChange={e => findUsers(e.currentTarget.value)}
+            />
+        </Popover>
+    </>
+
+
+
+export const CFieldSearch = connect(state => ({ usersRes: state.promise?.findUsersAll?.payload || [] }), { findUsers: actionSearchUsers })(FieldSearch) 

+ 95 - 5
src/components/main/Add.js

@@ -3,9 +3,8 @@ import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
 import { connect } from 'react-redux';
 import { Upload, message } from 'antd';
 import { backURL, gql } from '../../helpers';
-import { actionSetAvatar } from '../../actions';
-import { actionFullSetAvatar } from '../../redux/redux-thunk';
-import { Loo } from './Loo';
+import { actionUpdateAvatar } from '../../actions';
+import { Apps } from '../uploadPhoto';
 
 const Add = ({ imageUrl, onUploadFile }) => {
     const [loading, setLoading] = useState(false)
@@ -50,10 +49,101 @@ const Add = ({ imageUrl, onUploadFile }) => {
             <hr />
             <hr />
             <hr />
-            <Loo />
+            {/* <Apps /> */}
         </>
 
     )
 }
 
-export const CAdd = connect(state => ({ imageUrl: state?.myData?.avatar?.url }), { onUploadFile: actionFullSetAvatar })(Add)
+export const CAdd = connect(state => ({ imageUrl: state?.myData?.avatar?.url }), { onUploadFile: actionUpdateAvatar })(Add)
+
+
+
+
+// function getBase64(file) {
+//   return new Promise((resolve, reject) => {
+//     const reader = new FileReader();
+//     reader.readAsDataURL(file);
+//     reader.onload = () => resolve(reader.result);
+//     reader.onerror = error => reject(error);
+//   });
+// }
+
+// class PicturesWall extends React.Component {
+//   state = {
+//     previewVisible: false,
+//     previewImage: '',
+//     fileList: [
+//       {
+//         uid: '-1',
+//         name: 'image.png',
+//         status: 'done',
+//         url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+//       },
+//       {
+//         uid: '-2',
+//         name: 'image.png',
+//         status: 'done',
+//         url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+//       },
+//       {
+//         uid: '-3',
+//         name: 'image.png',
+//         status: 'done',
+//         url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+//       },
+//       {
+//         uid: '-4',
+//         name: 'image.png',
+//         status: 'done',
+//         url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+//       },
+//       {
+//         uid: '-5',
+//         name: 'image.png',
+//         status: 'error',
+//       },
+//     ],
+//   };
+
+//   handleCancel = () => this.setState({ previewVisible: false });
+
+//   handlePreview = async file => {
+//     if (!file.url && !file.preview) {
+//       file.preview = await getBase64(file.originFileObj);
+//     }
+
+//     this.setState({
+//       previewImage: file.url || file.preview,
+//       previewVisible: true,
+//     });
+//   };
+
+//   handleChange = ({ fileList }) => this.setState({ fileList });
+
+//   render() {
+//     const { previewVisible, previewImage, fileList } = this.state;
+//     const uploadButton = (
+//       <div>
+//         <Icon type="plus" />
+//         <div className="ant-upload-text">Upload</div>
+//       </div>
+//     );
+//     return (
+//       <div className="clearfix">
+//         <Upload
+//           action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
+//           listType="picture-card"
+//           fileList={fileList}
+//           onPreview={this.handlePreview}
+//           onChange={this.handleChange}
+//         >
+//           {fileList.length >= 8 ? null : uploadButton}
+//         </Upload>
+//         <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
+//           <img alt="example" style={{ width: '100%' }} src={previewImage} />
+//         </Modal>
+//       </div>
+//     );
+//   }
+// }

+ 11 - 0
src/components/main/DateCreated.js

@@ -0,0 +1,11 @@
+import React from 'react'
+
+export const DateCreated = ({ date = '' }) => {
+    const newDate = new Date(date * 1)
+    const resultDate = new Intl.DateTimeFormat('default').format(newDate)
+    return (
+        <>
+            {resultDate}
+        </>
+    )
+}

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

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

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

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

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

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

+ 4 - 0
src/components/main/new/styles.css

@@ -0,0 +1,4 @@
+.App {
+  font-family: sans-serif;
+  text-align: center;
+}

+ 45 - 0
src/components/main/new/styles.scss

@@ -0,0 +1,45 @@
+// .App {
+//     font-family: sans-serif;
+//     text-align: center;
+// }
+
+// .handle {
+//     display: block;
+//     width: 18px;
+//     height: 18px;
+//     margin-right: 20px;
+//     overflow: hidden;
+
+//     > svg {
+//         opacity: 0.3;
+//     }
+
+//     cursor: grab;
+// }
+// .qq {
+//     float: left;
+//     padding-left: 8px;
+//     padding-right: 8px;
+//     width: calc(25% - 16px);
+//     .content {
+//         padding: 8px 12px;
+//         background-color: #ddd;
+//         height: 150px;
+//         background-color: blue;
+//     }
+// }
+// .ww {
+//     background-color: #ddd;
+//     margin-left: -8px;
+//     margin-right: -8px;
+//     white-space: nowrap;
+//     &:after {
+//         content: "";
+//         clear: both;
+//         display: table;
+//     }
+// }
+// .rrr {
+//     width: 600px;
+//     margin: 0 auto;
+// }

+ 67 - 0
src/components/main/postsFeed/FieldComment.jsx

@@ -0,0 +1,67 @@
+import React, { useState } from 'react'
+import Text from 'antd/lib/typography/Text'
+import TextArea from 'antd/lib/input/TextArea'
+import { SendOutlined, } from '@ant-design/icons'
+import { connect } from 'react-redux'
+import { Button, Col, Row } from 'antd'
+import { actionAddSubComment, actionFullAddComment } from '../../../actions'
+
+
+const FieldCommentSend = ({ id, sentComment, autoFocus, value = '', setOpen }) => {
+    const [commentValue, setCommentValue] = useState(value)
+    const [error, setError] = useState(false)
+
+    const changeComentTextarea = (e) => {
+        setCommentValue(e.currentTarget.value)
+        setError(false)
+    }
+    const sendCommentValid = (value) => {
+        if (value.trim() !== '') {
+            sentComment(id, value.trim())
+            setCommentValue('')
+        } else {
+            setError(true)
+        }
+    }
+
+    const handlerClickBtn = () => {
+        sendCommentValid(commentValue)
+        setOpen(false)
+    }
+    
+    const onKeyPressHandler = e => {
+        if (e.charCode === 13) {
+            sendCommentValid(commentValue)
+            setOpen(false)
+        }
+    }
+
+    return (
+        <>
+            {error && <Text type='danger'>Field is required</Text>}
+            <Row align='middle' className='Post__send-comment'>
+                <Col flex='auto' offset={1}>
+                    <TextArea value={commentValue}
+                        autoFocus={autoFocus || false}
+                        placeholder="Add a comment ..."
+                        autoSize={{ minRows: 1, maxRows: 2 }}
+                        onChange={changeComentTextarea}
+                        bordered={false}
+                        onKeyPress={onKeyPressHandler}
+                    />
+                </Col>
+                <Col span={2}>
+                    <Button
+                        onClick={handlerClickBtn}
+                        icon={< SendOutlined
+                            style={{ fontSize: '1.2em', opacity: .6 }} />}
+                    />
+                </Col>
+            </Row>
+        </>
+    )
+}
+
+export const CFieldCommentSend = connect(null, { sentComment: actionFullAddComment })(FieldCommentSend)
+
+export const CFieldSubCommentSend = connect(null, { sentComment: actionAddSubComment })(FieldCommentSend)

+ 65 - 0
src/components/main/postsFeed/PostImage.jsx

@@ -0,0 +1,65 @@
+import { LeftCircleOutlined, RightCircleOutlined } from "@ant-design/icons";
+import { Carousel, Empty } from "antd";
+import React, { createRef } from "react";
+import nodata from '../../../images/nodata.png'
+import { backURL } from '../../../helpers/index'
+
+
+
+class PostImage extends React.Component {
+    constructor(props) {
+        super(props);
+        this.carouselRef = createRef();
+        this.state = {
+            movePrev: false,
+            moveNext: false
+        }
+    }
+
+    handleNext = () => this.carouselRef.current.next(this);
+
+    handlePrev = () => this.carouselRef.current.prev(this);
+
+    moveOnDivArray = (length, index) => {
+        if (length === 1) {
+            this.setState({ movePrev: false, moveNext: false })
+        } else if (index === 0) {
+            this.setState({ movePrev: false, moveNext: true })
+        } else if (index === length - 1 && length > 1) {
+            this.setState({ movePrev: true, moveNext: false })
+        } else {
+            this.setState({ movePrev: true, moveNext: true })
+        }
+    }
+
+    downOnDivArray = () => this.setState({ movePrev: false, moveNext: false })
+
+    render() {
+
+        const { images } = this.props
+        return (
+            <Carousel ref={this.carouselRef}
+                effect="fade"
+                infinite={false}
+                dots={{ className: 'Post__dots' }
+                }>
+                {!!images ?
+                    images.map((i, index) => i?.url ? <div
+                        key={i._id}
+                        onMouseEnter={() => this.moveOnDivArray(images.length, index)}
+                        onMouseLeave={this.downOnDivArray}>
+                        <button onClick={() => this.handlePrev()}
+                            className={`Post__prev Post__btn ${this.state.movePrev ? '--active' : ''}`}><LeftCircleOutlined /></button>
+                        <button onClick={() => this.handleNext()}
+                            className={`Post__next Post__btn ${this.state.moveNext ? '--active' : ''}`}><RightCircleOutlined /></button>
+                        <img src={backURL + '/' + i.url} />
+                    </div> :
+                        <Empty key={i._id} image={nodata} description={false} />) :
+                    <Empty image={nodata} description={false} />
+                }
+            </Carousel >
+        );
+    }
+}
+
+export default PostImage

+ 60 - 0
src/components/main/postsFeed/PostUserPanel.jsx

@@ -0,0 +1,60 @@
+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 { CModalPostLiked } from "../profilePage/ModalFollow"
+
+
+const HeartLike = ({ styleFontSize, likeStatus, changeLike }) =>
+    <Button
+        onClick={() => changeLike()}
+        type="none"
+        shape="circle"
+        icon={
+            likeStatus
+                ? <HeartFilled style={{ color: '#ff6969', fontSize: `${styleFontSize}` }} />
+                : <HeartOutlined style={{ color: '#1890ff', fontSize: `${styleFontSize}` }} />
+        }
+    />
+
+const PostUserPanel = ({ myID, postId = '', likes = [], styleFontSize, addLikePost, removeLikePost }) => {
+    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 ? removeLikePost(likeId, postId) : addLikePost(postId)
+
+
+    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} />
+                </Col>
+                <Col offset={0.5}>
+                    {!!likes.length && <button onClick={() => { setOpen(true) }}>Likes:<strong>{` ${likes.length}`}</strong></button>}
+                </Col>
+            </Row>
+        </>
+    )
+}
+
+export const CPostUserPanel = connect(state => ({
+    myID: state.auth.payload.sub.id || ''
+}), {
+    addLikePost: actionLikePost,
+    removeLikePost: actionDelLikePost,
+})(PostUserPanel)

+ 62 - 0
src/components/main/profilePage/ModalFollow.js

@@ -0,0 +1,62 @@
+import { Col, List, Row, Skeleton } from 'antd';
+import Modal from 'antd/lib/modal/Modal'
+import { useEffect } from 'react';
+import { connect } from 'react-redux';
+import { Link } from 'react-router-dom';
+import { actionFindFollowers, actionFindFollowing, actionFindLiked } from '../../../actions';
+import { UserAvatar } from '../../../pages/Header';
+
+
+const ModalFolower = ({ id, status, statusModal, data, title, follow }) => {
+    const handleCancel = () => statusModal(false);
+
+    useEffect(() => {
+        follow(id)
+    }, [])
+    const newData = data.map(d => d.owner ? d.owner : d)
+    return (
+        <Modal className='Modal'
+            title={title}
+            visible={true}
+            destroyOnClose={true}
+            footer={null}
+            onCancel={handleCancel}>
+            {status !== 'RESOLVED'
+                ? <Skeleton className='Modal__inner' avatar active paragraph={{ rows: 0 }} />
+                : <List className='Modal__inner'
+                    itemLayout="horizontal"
+                    dataSource={newData}
+                    renderItem={item => (
+                        <List.Item >
+                            <Link to={`/profile/${item._id}`} style={{ width: '100%' }} onClick={handleCancel}>
+                                <Row align='middle' >
+                                    <Col>
+                                        <UserAvatar avatar={item.avatar} avatarSize={'40px'} />
+                                    </Col>
+                                    <Col offset={2}>{item.nick || item.login || 'No Name'}</Col>
+                                </Row>
+                            </Link>
+                        </List.Item>
+                    )}
+                />}
+        </Modal>
+    )
+}
+
+export const CModalFollowers = connect(state => ({
+    id: state?.postsFeed?.userData?._id,
+    data: state?.promise?.findFollow?.payload?.followers || [],
+    status: state?.promise?.findFollow?.status
+}), { follow: actionFindFollowers })(ModalFolower)
+
+export const CModalFollowing = connect(state => ({
+    id: state?.postsFeed?.userData?._id,
+    data: state?.promise?.findFollow?.payload?.following || [],
+    status: state?.promise?.findFollow?.status
+}), { follow: actionFindFollowing })(ModalFolower)
+
+export const CModalPostLiked = connect(state => ({
+    data: state?.promise?.findLiked?.payload || [],
+    status: state?.promise?.findLiked?.status
+}), { follow: actionFindLiked })(ModalFolower)
+

+ 124 - 0
src/components/uploadPhoto/index.js

@@ -0,0 +1,124 @@
+import { DeleteOutlined, EyeOutlined, InboxOutlined, LoadingOutlined, PlusOutlined } from "@ant-design/icons";
+import { Button, message, Icon, Upload, Image } from "antd";
+import Dragger from "antd/lib/upload/Dragger";
+import React, { useState } from "react";
+import ReactDOM from "react-dom";
+import ImgCrop from 'antd-img-crop';
+import {
+    arrayMove,
+    SortableContainer,
+    SortableElement,
+    SortableHandle
+} from "react-sortable-hoc";
+import { backURL } from "../../helpers";
+
+
+const SortableItemMask = ({ removePhotosItem, setVisible, id }) =>
+    <div className="SortableItemMask">
+        <Button type="link" onClick={() => setVisible(true)}>
+            <EyeOutlined />
+        </Button>
+        <Button type="link" onClick={() => removePhotosItem(id)}>
+            <DeleteOutlined />
+        </Button>
+    </div>
+
+
+const Handle = SortableHandle(({ tabIndex, value, removePhotosItem }) => {
+    const [visible, setVisible] = useState(false);
+    return (
+        <div className="Handle" tabIndex={tabIndex} >
+            <SortableItemMask id={value._id} setVisible={setVisible} removePhotosItem={removePhotosItem} />
+            <img src={`${backURL + '/' + value.url}`} alt="avatar" style={{ width: '100%' }} />
+            <Image className="hidden-item"
+                width={200}
+                style={{ display: 'none' }}
+                preview={{
+                    visible,
+                    src: backURL + '/' + value.url,
+                    onVisibleChange: value => {
+                        setVisible(value);
+                    },
+                }}
+            />
+        </ div>
+    )
+})
+
+
+const SortableItem = SortableElement(props => {
+    const { value, removePhotosItem } = props
+    return (
+        <div className="SortableItem">
+            <Handle value={value} removePhotosItem={removePhotosItem} />
+        </div >
+    );
+});
+
+const props = {
+    name: 'photo',
+    action: `${backURL}/upload`,
+    headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {},
+}
+
+const SortableList = SortableContainer(({ items,...restProps }) => {
+    return (
+        <div className='SortableList'>
+            {items.map((item, index) => (
+                <SortableItem
+                    key={`item-${item._id}`}
+                    index={index}
+                    value={item}
+                    {...restProps}
+                />
+            ))}
+        </div>
+    );
+});
+
+
+export function EditPhotos({ photos, setPhotos }) {
+
+    const handlerChange = async ({ file }) => {
+        if (file.status === 'done') {
+
+            setPhotos([...photos, file.response])
+            message.success(`${file.name} file uploaded successfully`);
+        } else if (file.status === 'error') {
+            message.error(`${file.name} file upload failed.`);
+        }
+    }
+    const removePhotosItem = (id) => setPhotos(photos.filter(p => p._id !== id))
+
+    const onSortEnd = ({ oldIndex, newIndex }) => {
+        setPhotos(arrayMove(photos, oldIndex, newIndex));
+    };
+
+    return (
+        <div className="EditPhotos" >
+            {photos.length >= 8 ? null
+                : <Dragger {...props} className="EditPhotos__box"
+                    multiple={true}
+                    listType="picture-card"
+                    showUploadList={false}
+                    onChange={handlerChange}>
+                    <p className="ant-upload-drag-icon">
+                        <InboxOutlined />
+                    </p>
+                    <p className="ant-upload-text">Click or drag file to this area to upload</p>
+                </Dragger>
+            }
+            <></>
+            <SortableList
+                shouldUseDragHandle={true}
+                useDragHandle
+                axis="xy"
+                removePhotosItem={removePhotosItem}
+                items={photos}
+                onSortEnd={onSortEnd}
+            />
+        </div>
+    );
+}
+
+

+ 27 - 2
src/helpers/index.js

@@ -1,3 +1,9 @@
+import Icon from '@ant-design/icons';
+import { useEffect } from 'react';
+import { connect } from 'react-redux';
+import { Route } from 'react-router-dom';
+
+
 
 export const jwtDecode = (token) => {
     try {
@@ -6,7 +12,7 @@ export const jwtDecode = (token) => {
         return JSON.parse(base64Token)
     }
     catch (e) {
-        console.log('Лажа, Бро ' + e);
+        console.log('Ой, ошибочка вышла ' + e);
     }
 }
 
@@ -29,4 +35,23 @@ const getGQL = url =>
         return a.data[Object.keys(a.data)[0]]
     }
 
-export const gql = getGQL(backURL + '/graphql');
+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>
+    </svg>
+
+export const CircularGalleryIcon = props => <Icon component={CircularGallerySvg} {...props} />
+
+const RRoute = ({ action, component: Component, ...routeProps }) => {
+    const WrapperComponent = (componentProps) => {
+        useEffect(() => {
+            action(componentProps.match)
+        })
+        return <Component {...componentProps} />
+    }
+    return <Route {...routeProps} component={WrapperComponent} />
+}
+
+export const CRRoute = connect(null, { action: match => ({ type: 'ROUTE', match }) })(RRoute)

二進制
src/images/profile-post-no.jpeg


+ 3 - 3
src/index.js

@@ -5,9 +5,9 @@ import App from './App';
 import reportWebVitals from './reportWebVitals';
 
 ReactDOM.render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>,
+
+    <App />,
+
   document.getElementById('root')
 );
 

+ 35 - 0
src/pages/AllPosts.jsx

@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { actionAllPosts, actionRemovePostsFeedAC } from '../actions';
+import { CPosts } from '../components/Posts';
+
+const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(() => {
+        if (checkScroll) {
+            onAllPosts()
+            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 (
+        <CPosts />
+    )
+}
+
+export const CAllPosts = connect(state => ({ posts: state?.postsFeed?.posts || [] }), { onAllPosts: actionAllPosts, postsRemove: actionRemovePostsFeedAC, })(AllPosts)

+ 6 - 4
src/pages/Content.js

@@ -4,16 +4,18 @@ import React from 'react'
 
 
 
-export const Main = ({ children }) =>
-    <Row justify='center' className='Main'>
-        <Col xs={{ span: 24 }} sm={{ span: 20 }} md={{ span: 16 }} lg={{ span: 14 }} xl={{span:12}}className='Main__inner'>
+export const Container = ({ children }) =>
+    <Row justify='center' className='Main__inner'>
+        <Col xs={{ span: 24 }} sm={{ span: 20 }} md={{ span: 16 }} lg={{ span: 14 }} xl={{ span: 12 }} >
             {children}
         </Col>
     </Row>
 
 
+export const Main = ({ children }) =>
+    <div className='Main'>{children}</div>
+
 
-// export const CMain = connect(null, { postsFollowing: myFolowingPosts })(Main)
 
 
 export const Content = ({ children }) =>

+ 166 - 0
src/pages/EntityEditorPost.jsx

@@ -0,0 +1,166 @@
+import { EditOutlined } from "@ant-design/icons"
+import { Button, Divider, Input } from "antd"
+import TextArea from "antd/lib/input/TextArea"
+import Paragraph from "antd/lib/typography/Paragraph"
+import Text from "antd/lib/typography/Text"
+import Title from "antd/lib/typography/Title"
+import { useEffect, useState } from "react"
+import { connect } from "react-redux"
+import { actionSentPost, actionsentPostAC } from "../actions"
+import { EditPhotos } from "../components/uploadPhoto"
+
+
+const ContainEditorPost = ({ children }) =>
+    <div className='ContainErditorPost'>{children}</div>
+
+
+const EditTitlePost = ({ titleSend, setTitleSend }) => {
+    const [title, setTitle] = useState(titleSend || 'Enter title')
+    const [error, setError] = useState(false)
+    const [editMode, setEditMode] = useState(false)
+
+    const addTaskHandler = () => {
+        if (title.trim() !== '') {
+            setTitleSend(title.trim())
+            setEditMode(false)
+        } else {
+            setError(true)
+            setTitleSend('')
+        }
+    }
+    const titleInputHandler = () => {
+        setEditMode(true)
+    }
+
+    const titleInputHandlerClose = () => {
+        addTaskHandler()
+    }
+
+    const onChangeTask = (e) => {
+        setTitle(e.currentTarget.value)
+        setError(false)
+    }
+
+    const onKeyPressAddTask = (e) => {
+        if (e.charCode === 13) {
+            addTaskHandler()
+        }
+    }
+    return (
+        <>
+            <Divider orientation="left" orientationMargin="0">
+                <Title level={3}>Title
+                    <Button type="link" onClick={titleInputHandler}><EditOutlined /></Button></Title>
+            </Divider>
+            {error && <Text type="danger">Field must not be empty</Text>}
+            {editMode
+                ? <Input
+                    value={title}
+                    placeholder="title"
+                    onChange={onChangeTask}
+                    // className={s.input + ' ' + (error && s.error)}
+                    autoFocus onBlur={titleInputHandlerClose}
+                    onKeyPress={onKeyPressAddTask}
+                />
+                : <Title level={5} onDoubleClick={titleInputHandler}>
+                    {title}
+                </Title>
+            }
+        </>)
+}
+
+
+const EditDescriptionPost = ({ description, setDescription }) => {
+    const [text, setText] = useState(description || 'Enter descriptin');
+    const [error, setError] = useState(false)
+    const [editMode, setEditMode] = useState(false)
+
+
+    const addTaskHandler = () => {
+        if (text.trim() !== '') {
+            setDescription(text)
+            setEditMode(false)
+        } else {
+            setError(true)
+            setDescription('')
+        }
+    }
+    const textInputHandler = () => {
+        setEditMode(true)
+    }
+
+    const textInputHandlerClose = () => {
+        addTaskHandler()
+    }
+
+    const onChangeTask = (e) => {
+        setText(e.currentTarget.value)
+        setError(false)
+    }
+
+    const onKeyPressAddTask = (e) => {
+        if (e.shiftKey && e.charCode === 13) {
+            setText(text += `'\n'`)
+        } else if (e.charCode === 13) {
+            addTaskHandler()
+        }
+    }
+    return (
+        <>
+            <Divider orientation="left" orientationMargin="0">
+                <Title level={3}>Description
+                    <Button type="link" onClick={textInputHandler}><EditOutlined /></Button></Title>
+            </Divider>
+            {error && <Text type="danger">Field must not be empty</Text>}
+            {editMode
+                ? <TextArea
+                    placeholder="Description"
+                    autoSize={{ minRows: 3, maxRows: 5 }}
+                    value={text}
+                    onChange={onChangeTask}
+                    autoFocus onBlur={textInputHandlerClose}
+                    onKeyPress={onKeyPressAddTask}
+                />
+                : <Paragraph className="description" onDoubleClick={textInputHandler}>
+                    {text}
+                </Paragraph>
+            }
+        </>)
+}
+
+const EntityEditorPost = ({ entity = { array: [] }, onSave }) => {
+
+    const [photos, setPhotos] = useState([]);
+    const [titleSend, setTitleSend] = useState('')
+    const [description, setDescription] = useState('');
+    //  photos.length && titleSend && description ? : true
+    const disabledBtn = false
+    const sentPost = () => {
+        const [newphotos ]= photos.map(ph => {
+            return { _id: ph._id }
+        })
+
+
+        onSave(newphotos, titleSend, description);
+    }
+    // const sentPost = () => {
+    //     const newphotos = photos.map(ph => {
+    //         return { _id: ph._id }
+    //     })
+
+    //     console.log(result)
+
+    // }
+    return (
+        <ContainEditorPost>
+            <h1 className="title" level={1}>Create / edit Post</h1>
+            <Divider orientation="left" orientationMargin="0"><Title level={3}>Photos</Title></Divider>
+            <EditPhotos photos={photos} setPhotos={setPhotos} />
+            <EditTitlePost titleSend={titleSend} setTitleSend={setTitleSend} />
+            <EditDescriptionPost description={description} setDescription={setDescription} />
+            <Divider orientation="right">   <Button disabled={disabledBtn} type="primary" onClick={sentPost}>Send a Post</Button></Divider>
+        </ContainEditorPost>
+    )
+}
+
+export const CEntityEditorPost = connect(null, { onSave: actionSentPost })(EntityEditorPost)

+ 14 - 13
src/components/header/Header.jsx

@@ -1,20 +1,19 @@
 import React from 'react'
-import logo from '../../logo.svg';
-import noAva from '../../images/noAva.png'
+import logo from '../logo.svg';
+import noAva from '../images/noAva.png'
 import { Link } from 'react-router-dom';
-import { CFieldSearch } from './Search';
+import { CFieldSearch } from '../components/header/Search';
 import { connect } from 'react-redux';
-import { actionAuthLogout, actionProfilData } from '../../actions';
-import { backURL } from '../../helpers';
+import { actionAuthLogout } from '../actions';
+import { backURL } from '../helpers';
 import Layout, { Header } from 'antd/lib/layout/layout';
 import { Avatar, Col, Menu, Popover, Row } from 'antd';
 import { UserOutlined, CompassOutlined, SettingOutlined, HomeOutlined, ImportOutlined, MessageOutlined, PlusCircleOutlined } from '@ant-design/icons/lib/icons';
 
-const UserNav = ({ id }) => {
-    return <div className='UserNav'>
+const UserNav = () =>
+    <div className='UserNav'>
         <CUserNavIcon />
     </div>
-}
 
 export const UserAvatar = ({ avatarSize, avatar }) => {
     return (
@@ -32,10 +31,11 @@ export const UserAvatar = ({ avatarSize, avatar }) => {
     )
 }
 
+
 const ProfileDropMenu = ({ myID, onLogOut }) =>
     <Menu className='dropMenu'>
         <Menu.Item key={'0'}>
-            <Link to={`${myID}`}><UserOutlined /> My Profile</Link>
+            <Link to={`/profile/${myID}`}><UserOutlined /> My Profile</Link>
         </Menu.Item>
         <Menu.Item key={'1'}>
             <Link to={'/'}><SettingOutlined /> Settings</Link>
@@ -48,6 +48,7 @@ const ProfileDropMenu = ({ myID, onLogOut }) =>
 
 const CProfileDropMenu = connect(null, { onLogOut: actionAuthLogout })(ProfileDropMenu)
 
+
 const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
     <Row justify="end" align="middle" className='Header__userNav'>
         <Col >
@@ -60,7 +61,7 @@ const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
             <Link to='/add'><PlusCircleOutlined /></Link>
         </Col>
         <Col >
-            <Link to='/rar'><CompassOutlined /></Link>
+            <Link to='/all'><CompassOutlined /></Link>
         </Col>
         <Col>
             <Popover placement="bottomRight" content={<CProfileDropMenu myID={_id} />} trigger={'click'}>
@@ -70,15 +71,15 @@ const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
         </Col>
     </Row >
 
-const CUserNav = connect(state => ({ id: state.auth?.payload.sub.id || {} }))(UserNav)
-
 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: 1, width: '100%' }}>
@@ -90,7 +91,7 @@ const HeaderComponent = () =>
                     <CFieldSearch />
                 </Col>
                 <Col span={8}>
-                    <CUserNav />
+                    <UserNav />
                 </Col>
             </Row>
         </Header>

+ 98 - 0
src/pages/MainPostsFeed.jsx

@@ -0,0 +1,98 @@
+import { Card, Col, Row, Divider } from 'antd'
+import React, { useEffect, useState } from 'react'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import { UserAvatar } from './Header'
+import Paragraph from 'antd/lib/typography/Paragraph'
+import Text from 'antd/lib/typography/Text'
+import { actionPostsFeed, actionRemovePostsFeedAC } from '../actions'
+import { DateCreated } from '../components/main/DateCreated'
+import PostImage from '../components/main/postsFeed/PostImage'
+import { CPostUserPanel } from '../components/main/postsFeed/PostUserPanel'
+
+export const PostTitle = ({ owner }) =>
+    <Row justify="start" align='middle'>
+        <Link to={`/profile/${owner?._id}`} className='owner'>
+            <UserAvatar avatar={owner?.avatar} avatarSize={'45px'} />
+            <span className='nick'>{owner?.nick ? owner.nick : owner?.login ? owner.login : 'Null'}</span>
+        </Link >
+    </Row>
+
+
+export const PostDescription = ({ title, description, date }) =>
+    <>
+        <Row justify='space-between'>
+            <Col >
+                {!!title && <Text level={3} strong>{title}</Text>}
+            </Col>
+            <Col >
+                <Text type='secondary'>
+                    <DateCreated date={date} />
+                </Text>
+            </Col>
+        </Row>
+        <Paragraph ellipsis={true ? { rows: 1, expandable: true, symbol: '...' } : false}>
+            {description}
+        </Paragraph>
+    </>
+
+export const Comments = ({ comments = [], _id }) =>
+        <Link to={`/post/${_id}`}>
+            <Divider orientation="left">
+                {comments?.length ? `View ${comments.length} comments` : 'No comments'}
+            </Divider>
+        </Link>
+
+const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes } }) =>
+    <div className='Post'>
+        <Card
+            title={<PostTitle owner={owner} />}
+            cover={<PostImage images={images} />}
+        >
+            <CPostUserPanel postId={_id} likes={likes} styleFontSize='1.7em' />
+            <PostDescription title={title} description={text} date={createdAt} />
+            <Comments comments={comments} _id={_id} />
+        </Card>
+    </div>
+
+
+const MainPostsFeed = ({ posts, postsFollowing, postsFollowingRemove, following }) => {
+
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(() => {
+        if (checkScroll && following.length !== 0) {
+            postsFollowing()
+            setCheckScroll(false)
+        }
+    }, [checkScroll, following])
+
+    useEffect(() => {
+        document.addEventListener('scroll', scrollHandler)
+        return () => {
+            document.removeEventListener('scroll', scrollHandler)
+            postsFollowingRemove()
+
+        }
+    }, [])
+
+    const scrollHandler = (e) => {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+            setCheckScroll(true)
+        }
+    }
+
+    return (
+        <>
+            {Array.isArray(posts) && posts.map(p => <Post key={p._id} postData={p} />)}
+        </>
+    )
+}
+
+export const CMainPostsFeed = connect(state => ({
+    posts: state?.postsFeed?.posts || [],
+    following: state?.myData.following || []
+}), {
+    postsFollowing: actionPostsFeed,
+    postsFollowingRemove: actionRemovePostsFeedAC,
+})(MainPostsFeed)

+ 142 - 0
src/pages/PostPage.jsx

@@ -0,0 +1,142 @@
+import React, { useState } from 'react'
+import { Divider } from 'antd';
+import { connect } from 'react-redux'
+import PostImage from '../components/main/postsFeed/PostImage'
+import { PostTitle, PostDescription } from './MainPostsFeed';
+import Text from 'antd/lib/typography/Text';
+import { CFieldCommentSend, CFieldSubCommentSend } from '../components/main/postsFeed/FieldComment';
+import { CPostUserPanel } from '../components/main/postsFeed/PostUserPanel';
+import { Comment, Tooltip } from 'antd';
+import moment from 'moment';
+import { UserAvatar } from './Header';
+import { Link } from 'react-router-dom';
+import { LikeFilled, LikeOutlined } from '@ant-design/icons';
+import { actionLikeComment, actionDelLikeComment, actionSubComment } from '../actions';
+
+
+const PostPageTitle = ({ data: { owner } }) =>
+    <PostTitle owner={owner} />
+
+const CPostPageTitle = connect(state => ({ data: state?.postsFeed?.posts || {} }))(PostPageTitle)
+
+
+const PostCommentAuthor = ({ owner }) => {
+    return (
+        <Link className='PostCommentAuthor' to={`/profile/${owner?._id}`} >
+            {owner?.nick ? owner.nick : owner?.login ? owner.login : 'Null'}
+        </Link>
+    )
+}
+
+
+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 CPostComments = connect(state => ({
+    comments: state?.postsFeed?.posts?.comments || []
+}))(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>
+        <div className='PostOne__description-bottom'>
+            <Divider ></Divider>
+            <CPostUserPanel likes={likes} postId={_id}
+                styleFontSize='1.3em' />
+            <CFieldCommentSend postId={_id} setOpen={() => { }} />
+        </div>
+    </div>
+
+
+const CPostPageDescrption = connect(state => ({ data: state?.postsFeed?.posts || {} }))(PostPageDescrption)
+
+
+const PostPage = ({ data: { images } }) =>
+    <div className='PostOne'>
+        <div className='PostOne__inner'>
+            <div className='PostOne__image'>
+                <PostImage images={images} />
+            </div>
+            <div className='PostOne__title'>
+                <CPostPageTitle />
+            </div>
+            <div className='PostOne__description'>
+                <CPostPageDescrption />
+            </div>
+        </div>
+    </div>
+
+export const CPostPage = connect(state => ({ data: state?.postsFeed?.posts || {} }))(PostPage)

+ 125 - 0
src/pages/ProfilePage.jsx

@@ -0,0 +1,125 @@
+import React, { useEffect, useState } from 'react'
+import { Button, Card, Col, Row } from 'antd'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import {  actionProfilePageData, actionRemovePostsFeedAC, actionSubscribe, actionUnSubscribe } from '../actions'
+import { UserAvatar } from './Header'
+import { CModalFollowers, CModalFollowing } from '../components/main/profilePage/ModalFollow'
+import { DateCreated } from '../components/main/DateCreated'
+import Text from 'antd/lib/typography/Text'
+import { CPosts, CProfilePagePosts } from '../components/Posts'
+
+
+
+
+
+const ProfileFollow = ({ myID, userId, followers, onSubsuscribe, onUnSubsuscribe }) => {
+    const followCheck = followers.find(f => f._id === myID && true)
+    return (
+        <Col className='Profile__seting' offset={4}>
+            {!!followCheck ?
+                <Button onClick={() => onUnSubsuscribe(userId)}>UnSubscribe</Button> :
+                <Button onClick={() => onSubsuscribe(userId)} type="primary">Subscribe</Button>}
+        </Col>
+    )
+}
+
+const CProfileSetting = connect(state => ({
+    myID: state?.auth?.payload?.sub.id,
+    followers: state?.postsFeed?.userData?.followers || []
+}), { onSubsuscribe: actionSubscribe, onUnSubsuscribe: actionUnSubscribe })(ProfileFollow)
+
+
+const ProfilePageData = ({ data: { _id, avatar, login, nick, createdAt = '', followers, following }, count, setFollowing, setFollowers }) =>
+    <Row className='Profile' >
+        <Col span={8}>
+            <UserAvatar avatarSize={'150px'} avatar={avatar} />
+        </Col>
+        <Col span={14} offset={1}>
+            <Row align='top' className='Profile__name'>
+                <Col>
+                    <h1>{nick || login || 'No Name'}</h1>
+                    <span className='Profile__login'>{login || '----'}</span>
+                </Col>
+                <Col>
+                    <CProfileSetting userId={_id} />
+                </Col>
+            </Row>
+            <Row align='middle'>
+                <Col >
+                    <Text type='secondary'>Account created: <DateCreated date={createdAt} /></Text>
+                </Col>
+                <Col offset={2}>
+                    <Link className='Profile__link-message' to='/message'>Send message</Link>
+                </Col>
+            </Row>
+            <Row className='Profile__count' align='middle' justify='space-between'>
+                <Col >
+                    <strong>{count || '0'}</strong>
+                    <span>Posts</span>
+                </Col>
+                <Col >
+                    <Button type="link" onClick={() => setFollowers(true)}>
+                        <strong>{followers?.length || '0'}</strong>
+                        <span>Followers</span>
+                    </Button>
+                </Col>
+                <Col >
+                    <Button type="link" onClick={() => setFollowing(true)}>
+                        <strong>{following?.length || '0'}</strong>
+                        <span>Following</span>
+                    </Button>
+                </Col>
+            </Row>
+        </Col >
+    </Row >
+
+const CProfilePageData = connect(state => ({
+    data: state?.postsFeed?.userData || {},
+    count: state?.postsFeed?.count || null
+}))(ProfilePageData)
+
+
+
+
+const ProfilePage = ({ match: { params: { _id } }, getProfileUser, clearDataProfile }) => {
+    const [followers, setFollowers] = useState(false)
+    const [following, setFollowing] = useState(false)
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(() => {
+        document.addEventListener('scroll', scrollHandler)
+        return () => {
+            document.removeEventListener('scroll', scrollHandler)
+            setCheckScroll(true)
+            clearDataProfile()
+        }
+    }, [_id])
+
+    useEffect(() => {
+        if (checkScroll) {
+
+            getProfileUser(_id)
+            setCheckScroll(false)
+        }
+    }, [_id, checkScroll])
+
+    const scrollHandler = (e) => {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+            setCheckScroll(true)
+        }
+    }
+
+    return (
+        <>
+            <CProfilePageData setFollowing={setFollowing} setFollowers={setFollowers} />
+            {followers && < CModalFollowers statusModal={setFollowers} title={'Followers'} />}
+            {following && < CModalFollowing statusModal={setFollowing} title={'Following'} />}
+            <CPosts />
+        </>
+    )
+}
+
+export const CProfilePage = connect(state => ({
+    posts: state?.postsFeed?.posts || []
+}), { getProfileUser: actionProfilePageData, clearDataProfile: actionRemovePostsFeedAC })(ProfilePage)

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

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

+ 4 - 4
src/redux/auth-reducer.js

@@ -1,15 +1,15 @@
-import { jwtDecode } from '../helpers'
+import { jwtDecode } from '../../helpers'
 
 
 export const authReducer = (state, { type, token, remember }) => {
     if (!state) {
         if (localStorage.authToken || sessionStorage.authToken) {
-            type = 'AUTH_LOGIN'
+            type = 'AUTH-LOGIN'
             token = localStorage.authToken || sessionStorage.authToken
         } else state = {}
     }
 
-    if (type === 'AUTH_LOGIN') {
+    if (type === 'AUTH-LOGIN') {
         remember ?
             localStorage.setItem('authToken', token) :
             sessionStorage.setItem('authToken', token)
@@ -22,7 +22,7 @@ export const authReducer = (state, { type, token, remember }) => {
             }
         } else return state
     }
-    if (type === 'AUTH_LOGOUT') {
+    if (type === 'AUTH-LOGOUT') {
         localStorage.removeItem('authToken')
         sessionStorage.removeItem('authToken')
         return {}

+ 3 - 1
src/redux/myProfile-reducer.js

@@ -8,7 +8,9 @@ export const myProfileReducer = (state = {}, { type, data }) => {
         'ABOUTME-UPDATE-AVATAR': () => {
             return { ...state, avatar: { ...data } }
         },
-      
+        'UPDATE-MY-FOLLOWING': () => {
+            return { ...state, following: [...data] }
+        }
     }
     if (type in types) {
         return types[type]()

+ 96 - 0
src/redux/reducers/postFeed-reducer.js

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

src/redux/promise-reducer.js → src/redux/reducers/promise-reducer.js


+ 5 - 0
src/redux/reducers/route-reducer.js

@@ -0,0 +1,5 @@
+export const routeReducer = (state = {}, { type, match }) => {
+    if (type === 'ROUTE')
+        return match
+    return state
+}

+ 13 - 8
src/redux/redux-store.js

@@ -1,20 +1,25 @@
 import { createStore, combineReducers, applyMiddleware } from 'redux';
-import thunk from 'redux-thunk';
-import { authReducer } from './auth-reducer';
-import { myProfileReducer } from './myProfile-reducer';
-import { postsFeedReducer } from './postFeed-reducer';
-import { promiseReducer } from './promise-reducer';
-import { actionFullAboutMe } from './redux-thunk';
-
+import { authReducer } from './reducers/auth-reducer';
+import { myProfileReducer } from './reducers/myProfile-reducer';
+import { postsFeedReducer } from './reducers/postFeed-reducer';
+import { promiseReducer } from './reducers/promise-reducer';
+import createSagaMiddleware from 'redux-saga'
+import { rootSaga } from './saga';
+import { actionFullAboutMe } from '../actions'
+import { routeReducer } from './reducers/route-reducer';
 
+const sagaMiddleware = createSagaMiddleware()
 
 const store = createStore(combineReducers({
     auth: authReducer,
     promise: promiseReducer,
     myData: myProfileReducer,
     postsFeed: postsFeedReducer,
+    route: routeReducer,
 }),
-    applyMiddleware(thunk))
+    applyMiddleware(sagaMiddleware))
+
+sagaMiddleware.run(rootSaga)
 
 store.dispatch(actionFullAboutMe())
 

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


File diff suppressed because it is too large
+ 366 - 0
src/redux/saga/index.js


+ 58 - 7
yarn.lock

@@ -1049,7 +1049,7 @@
     core-js-pure "^3.19.0"
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
   integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
@@ -2314,6 +2314,14 @@ ansi-styles@^5.0.0:
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
   integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
 
+antd-img-crop@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/antd-img-crop/-/antd-img-crop-4.1.0.tgz#679872675c26122b8c86d0c5cf0fa8d43d9af6ae"
+  integrity sha512-41wH5kvn00fdWF1doN0MAXpTWjawUWbiOyDVuQGM8NIffs6YQ2ihgoVhaBMnbs9VCswrCAAHdo3nYL8B8Rg0tA==
+  dependencies:
+    "@babel/runtime" "^7.16.7"
+    react-easy-crop "^4.0.1"
+
 antd@^4.18.2:
   version "4.18.2"
   resolved "https://registry.yarnpkg.com/antd/-/antd-4.18.2.tgz#e4d03b2229b1f4b3b204e5e5539693aa653d3b7e"
@@ -2422,6 +2430,11 @@ array-includes@^3.1.3, array-includes@^3.1.4:
     get-intrinsic "^1.1.1"
     is-string "^1.0.7"
 
+array-move@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/array-move/-/array-move-4.0.0.tgz#2c3730f056cc926f62a59769a5a8cda2fb6d8c55"
+  integrity sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==
+
 array-tree-filter@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190"
@@ -4859,6 +4872,13 @@ internal-slot@^1.0.3:
     has "^1.0.3"
     side-channel "^1.0.4"
 
+invariant@^2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+  integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+  dependencies:
+    loose-envify "^1.0.0"
+
 ip@^1.1.0:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
@@ -5855,7 +5875,7 @@ lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
-loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
   integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -6114,6 +6134,11 @@ normalize-url@^6.0.1:
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
   integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
 
+normalize-wheel@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45"
+  integrity sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=
+
 npm-run-path@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -7073,6 +7098,15 @@ prompts@^2.0.1, prompts@^2.4.2:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
+prop-types@^15.5.7:
+  version "15.8.1"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+  integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+  dependencies:
+    loose-envify "^1.4.0"
+    object-assign "^4.1.1"
+    react-is "^16.13.1"
+
 prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.8.0"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.0.tgz#d237e624c45a9846e469f5f31117f970017ff588"
@@ -7547,6 +7581,14 @@ react-dom@^17.0.2:
     object-assign "^4.1.1"
     scheduler "^0.20.2"
 
+react-easy-crop@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/react-easy-crop/-/react-easy-crop-4.0.1.tgz#0f540086e20055c51920e443dca5f8f79bdf609c"
+  integrity sha512-cREis2557y/ZkvgiNaLlFrzjduUSUvEYYxbglwggpo2gnxCjBQZeRgAPoedvXX0e0BgyGAI0zD3motVucJGhzA==
+  dependencies:
+    normalize-wheel "^1.0.1"
+    tslib "2.0.1"
+
 react-error-overlay@^6.0.10:
   version "6.0.10"
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
@@ -7663,6 +7705,15 @@ react-scripts@5.0.0:
   optionalDependencies:
     fsevents "^2.3.2"
 
+react-sortable-hoc@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz#f6780d8aa4b922a21f3e754af542f032677078b7"
+  integrity sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==
+  dependencies:
+    "@babel/runtime" "^7.2.0"
+    invariant "^2.2.4"
+    prop-types "^15.5.7"
+
 react@^17.0.2:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -7722,11 +7773,6 @@ redux-saga@^1.1.3:
   dependencies:
     "@redux-saga/core" "^1.1.3"
 
-redux-thunk@^2.4.1:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
-  integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
-
 redux@^4.0.0, redux@^4.0.4, redux@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
@@ -8718,6 +8764,11 @@ tsconfig-paths@^3.11.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
+tslib@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
+  integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
+
 tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"