3 کامیت‌ها 0dd3f83e00 ... 5cd754ac44

نویسنده SHA1 پیام تاریخ
  makstravm 5cd754ac44 Merge branch 'master' of http://gitlab.a-level.com.ua/makstravm/HipstaGram 2 سال پیش
  makstravm 55316319b1 added page edit my data 2 سال پیش
  makstravm 1a0f30384e added page edit / create post - end 2 سال پیش

+ 12 - 21
src/App.js

@@ -4,22 +4,19 @@ import { Router, Route, Switch, Redirect } from 'react-router-dom';
 import createHistory from "history/createBrowserHistory";
 import { connect, Provider } from 'react-redux';
 import store from './redux/redux-store';
-import { Authorization } from './components/Authorization';
-import { Container, Content, Main } from './pages/Content';
+import { 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';
+import { SettingsPage } from './pages/SettingsPage';
+import { Authorization } from './pages/Authorization';
 
 export const history = createHistory()
 
-
-const Aside = () =>
-    <div>sdfsdgsgsdg</div>
 const AppContent = ({ isToken }) =>
     <Router history={history}>
         {!isToken
@@ -30,28 +27,22 @@ const AppContent = ({ isToken }) =>
                 <Redirect from='/*' to='/auth/login' />
             </Switch>
             :
-
             <Content>
                 <HeaderComponent />
                 <Main>
-                    <Container>
+                    <Switch>
                         <Route path='/' component={CMainPostsFeed} exact />
                         <Route path='/profile/:_id' component={CProfilePage} />
-                        <Route path='/message' component={Aside} />
-                        {/* <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='/' /> */}
+                        {/* <Route path='/message' component={Aside} /> */}
+                        <Route path='/edit/post/:_id' component={CEntityEditorPost} />
+                        <Route path='/my-settings' component={SettingsPage} />
+                        <Route path='/all' component={CAllPosts} />
+                        <CRRoute path='/post/:id' component={CPostPage} />
+                        <Redirect from='/*' to='/' />
+                        {/* <Redirect from='/*' to='/post/:id' /> */}
+                    </Switch>
                 </Main>
             </Content >
-
-
-            // <Switch>
-            //     <Route path='/' component={Content} exact />
-            //     <Redirect from='/auth/*' to='/' />
-            // </Switch>
         }
     </Router >
 

+ 88 - 15
src/App.scss

@@ -326,6 +326,12 @@ body {
     h1 {
         line-height: 1;
     }
+    a {
+        display: inline-block;
+    }
+    &__data {
+        padding-top: 10px;
+    }
     &__name {
         padding-bottom: 10px;
     }
@@ -341,6 +347,10 @@ body {
         span {
             font-size: 1.2em;
         }
+        button {
+            padding: 0;
+            border: none;
+        }
     }
     button {
         color: $defaultColorB;
@@ -363,6 +373,9 @@ body {
             background: #40a9ff;
         }
     }
+    &__created {
+        padding: 10px 0;
+    }
     &__post {
         padding: 2px;
         div {
@@ -388,7 +401,7 @@ body {
 
 .PostOne {
     max-width: 1250px;
-    padding: 0 15px;
+    padding: 40px 15px 0;
     margin: 0 auto;
 
     &__inner {
@@ -419,7 +432,8 @@ body {
         background-color: rgb(87, 87, 87);
         img {
             max-width: 100%;
-            max-height: 650px;
+            max-height: 550px;
+            min-height: 450px;
             margin: 0 auto;
             object-fit: cover;
         }
@@ -445,7 +459,7 @@ body {
         flex-direction: column;
         justify-content: space-between;
         height: 100%;
-        max-height: 565px;
+        max-height: 475px;
     }
     &__description-top {
         flex-grow: 1;
@@ -461,20 +475,21 @@ body {
     font-size: 1.2em;
     font-weight: 500;
 }
-
-.ContainErditorPost {
+.ContainerInner {
     width: 100%;
     background-color: $defaultColorW;
     padding: 15px 25px;
     box-shadow: 0 0 6px 2px rgba($defaultColorB, 0.5);
     border-radius: 5px;
+}
+.ContainEditPost {
     .title {
         width: 100%;
         text-align: center;
         display: inline-block;
     }
     .description {
-        white-space: pre;
+        white-space: break-spaces;
     }
 }
 .EditPhotos {
@@ -489,6 +504,7 @@ body {
     display: grid;
     grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
     grid-gap: 10px;
+    justify-items: center;
 }
 
 .SortableItemMask {
@@ -510,15 +526,6 @@ body {
             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 {
@@ -538,6 +545,17 @@ body {
             opacity: 1;
         }
     }
+    .hiden-item,
+    .ant-image-mask,
+    .ant-image {
+        display: none;
+        position: absolute;
+        top: 0;
+        left: 0;
+        clip: rect(0 0 0);
+        pointer-events: none;
+        padding: 10px;
+    }
 }
 .ant-image-preview-img-wrapper {
     display: flex;
@@ -547,3 +565,58 @@ body {
         max-height: 60vh;
     }
 }
+.ant-dropdown {
+    box-shadow: 0 0 2px 0 $defaultColorB;
+}
+.avatar-uploader > .ant-upload {
+    width: 150px;
+    height: 150px;
+    border-radius: 50%;
+    position: relative;
+    .edit-icon {
+        position: absolute;
+        top: 0;
+        right: 0;
+    }
+}
+
+.EditMyData {
+    label {
+        display: flex;
+        align-items: flex-end;
+        padding-bottom: 10px;
+        flex-wrap: nowrap;
+    }
+    h4 {
+        margin: 0;
+        padding-right: 10px;
+    }
+    button {
+        display: none;
+        margin: 0 auto;
+        &.--block {
+            display: block;
+        }
+    }
+    &__lable-box {
+        span {
+            display: block;
+            text-align: center;
+        }
+        input.--error {
+            border-color: #ff4d4f;
+            max-width: 100%;
+        }
+        .EditMyData__lable-text {
+            font-size: 1.3em;
+            display: inline-block;
+            padding: 0 10px;
+            span {
+                display: inline-block;
+            }
+            svg {
+                margin-left: 10px;
+            }
+        }
+    }
+}

+ 27 - 6
src/actions/index.js

@@ -7,7 +7,7 @@ import { gql } from "../helpers";
 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 actionClearPromise = (name) => ({ type: 'CLEAR-PROMISE', name })
 export const actionPromise = (name, promise) => ({ type: 'PROMISE_START', name, promise })
 
 
@@ -39,6 +39,8 @@ export const actionAboutMeAC = (data) => ({ type: 'ABOUTME-DATA-ADD', data })
 
 export const actionFullAboutMe = () => ({ type: 'ABOUT_ME' })
 
+export const actionFullAboutMeUpsert = (nick, login) => ({ type: 'ABOUT_ME_UPSERT', nick, login })
+
 export const actionAboutMe = (id) =>
     actionPromise('aboutMe', gql(`query userOned($myID:String!){
                         UserFindOne(query: $myID){
@@ -46,8 +48,16 @@ export const actionAboutMe = (id) =>
                             avatar { _id url }
                             following{ _id}
                         }
-                }`, { myID: JSON.stringify([{ ___owner: id }]) }))
+                }`, { myID: JSON.stringify([{ _id: id }]) }))
 
+export const actionUpsertAboutMe = (myData) =>
+    actionPromise('upsertAboutMe', gql(`mutation editAboutMe($user:UserInput){
+                        UserUpsert(user:$user){
+                            _id   _id  login nick
+                            avatar { _id url }
+                            following{ _id}
+                        }
+                }`, { user: myData }))
 
 //*************** Action Posts Feed ******************//
 
@@ -93,6 +103,8 @@ export const actionProfileDataAC = (postsData, count, userData) => ({ type: 'ADD
 
 export const actionProfilePageData = (id) => ({ type: 'DATA_PROFILE', id })
 
+export const actionFindPostOne = (_id) => ({ type: 'FIND_POST_ONE', _id })
+
 export const actionProfileData = (_id) =>
     actionPromise('userOneData', gql(` query userOned($id:String!){
                         UserFindOne(query: $id){
@@ -120,6 +132,15 @@ export const actionProfilePagePost = (_id, skip) => actionPromise('userOneDataPo
 }))
 
 
+export const actionPostOneEdit = (_id) =>
+    actionPromise('postOneEdit', gql(`query post($id:String!) {
+                    PostFindOne(query:$id) {
+                        _id title text 
+                        images { _id url}
+                  
+                        }
+                    }`, { id: JSON.stringify([{ _id }]) }))
+
 //****************---All FIND POSTS---*************************//
 
 export const actionAllPosts = () => ({ type: 'ALL_POSTS' })
@@ -336,7 +357,7 @@ export const actionGetAvatar = (id) =>
         UserFindOne(query: $myID) {
                             avatar { _id url }
         }
-    }`, { myID: JSON.stringify([{ ___owner: id }]) }))
+    }`, { myID: JSON.stringify([{ _id: id }]) }))
 
 
 //****************--- Find FOllowing/Follovwrs---*************************//
@@ -377,11 +398,11 @@ export const actionGetFindLiked = (_id) =>
 
 //****************---Create Post ---*************************/
 
-export const actionsentPost = (_id = '', photos, text, title) => ({ type: 'CREATE_POST', photos, text, title })
+export const actionFullSentPost = (images, title, text) => ({ type: 'CREATE_POST', images, text, title })
 
-export const actionSentPost = (photos, title, text, id = "undefined") =>
+export const actionSentPost = (upSertPostObj) =>
     actionPromise('sentPost', gql(`mutation sentPost($post: PostInput){
               PostUpsert(post: $post){
                     _id images{_id url}
                 }
-            }`, { post: { text, title, images: { _id: photos._id } } }))
+            }`, { post: upSertPostObj }))

+ 6 - 25
src/components/Authorization.jsx

@@ -1,13 +1,10 @@
-import React  from 'react'
-import authBg from '../images/authBg.png'
+import React from 'react'
 import { connect } from 'react-redux'
-import { NavLink} from 'react-router-dom'
-
-import { Form, Input, Button, Row, Col, Card, Divider, Checkbox } from 'antd';
+import { Form, Input, Button, Checkbox } from 'antd';
 import { UserOutlined, LockOutlined } from '@ant-design/icons';
 import { actionFullLogIn, actionFullRegister } from '../actions';
 
-const FormInput = ({ buttonTitle, onSignIn }) => {
+const FormAuthorization = ({ buttonTitle, onSignIn }) => {
     const onFinish = ({ login, password, remember }) => {
         onSignIn(login, password, remember)
     };
@@ -65,25 +62,9 @@ const FormInput = ({ buttonTitle, onSignIn }) => {
         </Form>
     )
 }
-const CLoginForm = connect(null, { onSignIn: actionFullLogIn})(FormInput)
-const CRegisterForm = connect(null, { onSignIn: actionFullRegister})(FormInput)
+export const CLoginForm = connect(null, { onSignIn: actionFullLogIn })(FormAuthorization)
+export const CRegisterForm = connect(null, { onSignIn: actionFullRegister })(FormAuthorization)
+
 
-export const Authorization = ({ match: { params: { _id } } }) => {
-    return (
-        <div className='Authorization' style={{ backgroundImage: `url(${authBg})` }}>
-            <Row justify="end" align="middle" className='Authorization__form'>
-                <Col >
-                    <Card style={{ width: 380 }} >
-                        <NavLink activeClassName='active' to={`/auth/login`}><span>Log In</span></NavLink>
-                        <Divider type="vertical" />
-                        <NavLink activeClassName='active' to={'/auth/registration'}>Registration</NavLink>
-                        <Divider>{_id === 'login' ? 'Log in' : 'Registration'}</Divider >
-                        {_id === 'login' ? <CLoginForm buttonTitle={'Sign In'} /> : <CRegisterForm buttonTitle={'Sign up'} />}
-                    </Card>
-                </Col>
-            </Row >
-        </div>
-    )
-}
 
 

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

@@ -2,9 +2,9 @@ import { Empty, Input, Popover } from 'antd'
 import React from 'react'
 import { connect } from 'react-redux';
 import { Link } from 'react-router-dom';
-import { actionFindUsers, actionSearchUsers } from '../../actions';
-import { UserAvatar } from '../../pages/Header';
+import {  actionSearchUsers } from '../../actions';
 import { SearchOutlined } from '@ant-design/icons';
+import { UserAvatar } from './UserAvatar';
 const FindUsersResult = ({ usersRes }) =>
     <div className='Header__search-drop' >
         {

+ 18 - 0
src/components/header/UserAvatar.js

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

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

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

+ 2 - 2
src/components/Posts.jsx

@@ -1,8 +1,8 @@
 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 postNoData from '../../images/profile-post-no.jpeg'
 import { Link } from "react-router-dom"
+import { backURL, CircularGalleryIcon } from "../../helpers"
 
 const Posts = ({ posts }) =>
     <Row gutter={[15, 15]}>

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

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

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

@@ -1,45 +0,0 @@
-// .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;
-// }

+ 32 - 0
src/components/main/post/PostTitle.js

@@ -0,0 +1,32 @@
+import { Col, Row, Button, Dropdown, Menu } from 'antd'
+import { MoreOutlined } from '@ant-design/icons'
+import { Link } from 'react-router-dom'
+import { connect } from 'react-redux'
+import { UserAvatar } from '../../header/UserAvatar'
+
+const MenuOverlay = ({ postId }) =>
+    <Menu>
+        <Menu.Item>
+            <Link to={`/edit/post/${postId}`}> Edit post</Link>
+        </Menu.Item>
+    </Menu >
+
+export const PostTitle = ({ owner, myID, postId }) =>
+    <Row justify="space-between" align='middle'>
+        <Col>
+            <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 >
+        </Col>
+        {owner?._id === myID && <Col>
+            <Dropdown overlay={<MenuOverlay postId={postId} />} placement="bottomRight" trigger={['click']}>
+                <Button type='link'><MoreOutlined style={{ fontSize: '1.4em' }} /></Button>
+            </Dropdown>
+
+        </Col>}
+    </Row>
+
+
+
+export const CPostTitle = connect(state => ({ myID: state?.auth?.payload?.sub?.id || '' }))(PostTitle)

+ 2 - 4
src/components/main/postsFeed/PostUserPanel.jsx

@@ -20,15 +20,13 @@ const HeartLike = ({ styleFontSize, likeStatus, changeLike }) =>
 
 const PostUserPanel = ({ myID, postId = '', likes = [], styleFontSize, addLikePost, removeLikePost }) => {
     const [open, setOpen] = useState(false)
-    let likeStatus
+    let likeStatus =false
     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)

+ 35 - 0
src/components/main/profilePage/EditAvatar.js

@@ -0,0 +1,35 @@
+import { EditOutlined,} from '@ant-design/icons';
+import { connect } from 'react-redux';
+import { Upload, message, } from 'antd';
+import {  propsUploadFile } from '../../../helpers';
+import { actionUpdateAvatar } from '../../../actions';
+import ImgCrop from 'antd-img-crop';
+import { UserAvatar } from '../../header/UserAvatar';
+
+
+const EditAvatar = ({ avatar, onUploadFile }) => {
+    const handleChange = ({ file }) => {
+        if (file.status === 'done') {
+            message.success(`${file.name} file uploaded successfully`);
+            onUploadFile(file.response)
+        } else if (file.status === 'error') {
+            message.error(`${file.name} file upload failed.`);
+        }
+    }
+    return (
+        <ImgCrop rotate shape='round'>
+            <Upload {...propsUploadFile}
+                listType="picture-card"
+                showUploadList={false}
+                onChange={handleChange}
+                className="avatar-uploader">
+                <UserAvatar avatarSize={'150px'} avatar={avatar} />
+                <span className='edit-icon'>
+                    <EditOutlined style={{ fontSize: '1.4em', color: '#1890ff ' }} />
+                </span>
+            </Upload>
+        </ImgCrop>
+    )
+}
+
+export const CEditAvatar = connect(state => ({ avatar: state?.myData?.avatar }), { onUploadFile: actionUpdateAvatar })(EditAvatar)

+ 1 - 1
src/components/main/profilePage/ModalFollow.js

@@ -4,7 +4,7 @@ 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';
+import { UserAvatar } from '../../header/UserAvatar';
 
 
 const ModalFolower = ({ id, status, statusModal, data, title, follow }) => {

+ 63 - 0
src/components/uploadPhoto/EditDescriptionPost.js

@@ -0,0 +1,63 @@
+import { EditOutlined } from "@ant-design/icons";
+import { Button, Divider } 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";
+
+
+
+export const EditDescriptionPost = ({ description, setDescription }) => {
+    const [text, setText] = useState(description || 'Enter descriptin');
+    const [error, setError] = useState(false)
+    const [editMode, setEditMode] = useState(false)
+
+    useEffect(() => {
+        setText(description || 'Enter descriptin')
+    }, [description]);
+
+    const addValueHandler = () => {
+        if (text.trim() !== '') {
+            setDescription(text)
+            setEditMode(false)
+        } else {
+            setError(true)
+            setDescription('')
+        }
+    }
+
+    const onChangeInput = (e) => {
+        setText(e.currentTarget.value)
+        setError(false)
+    }
+
+    const onKeyPressAdd = (e) => {
+        if (e.shiftKey && e.charCode === 13) {
+            setText(text += `'\n'`)
+        } else if (e.charCode === 13) {
+            addValueHandler()
+        }
+    }
+    return (
+        <>
+            <Divider orientation="left" orientationMargin="0">
+                <Title level={3}>Description
+                    <Button type="link" onClick={() => setEditMode(true)}><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={onChangeInput}
+                    autoFocus onBlur={() => addValueHandler()}
+                    onKeyPress={onKeyPressAdd}
+                />
+                : <Paragraph className="description" onDoubleClick={() => setEditMode(true)}>
+                    {text}
+                </Paragraph>
+            }
+        </>)
+}

+ 26 - 19
src/components/uploadPhoto/index.js

@@ -1,16 +1,14 @@
-import { DeleteOutlined, EyeOutlined, InboxOutlined, LoadingOutlined, PlusOutlined } from "@ant-design/icons";
-import { Button, message, Icon, Upload, Image } from "antd";
+import { DeleteOutlined, EyeOutlined, InboxOutlined } from "@ant-design/icons";
+import { Button, message, Image, Progress } 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";
+import { backURL, propsUploadFile } from "../../helpers";
 
 
 const SortableItemMask = ({ removePhotosItem, setVisible, id }) =>
@@ -55,13 +53,8 @@ const SortableItem = SortableElement(props => {
     );
 });
 
-const props = {
-    name: 'photo',
-    action: `${backURL}/upload`,
-    headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {},
-}
 
-const SortableList = SortableContainer(({ items,...restProps }) => {
+const SortableList = SortableContainer(({ items, ...restProps }) => {
     return (
         <div className='SortableList'>
             {items.map((item, index) => (
@@ -78,18 +71,20 @@ const SortableList = SortableContainer(({ items,...restProps }) => {
 
 
 export function EditPhotos({ photos, setPhotos }) {
-
+    const [progress, setProgress] = useState(0);
+    const [loading, setLoading] = useState(false);
     const handlerChange = async ({ file }) => {
-        if (file.status === 'done') {
-
-            setPhotos([...photos, file.response])
+        if (file.status === "uploading") {
+            setLoading(true)
+            setProgress(file.percent)
+        } else if (file.status === 'done') {
             message.success(`${file.name} file uploaded successfully`);
+            setPhotos([...photos, file.response])
         } 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));
     };
@@ -97,7 +92,8 @@ export function EditPhotos({ photos, setPhotos }) {
     return (
         <div className="EditPhotos" >
             {photos.length >= 8 ? null
-                : <Dragger {...props} className="EditPhotos__box"
+                : <Dragger {...propsUploadFile}
+                    className="EditPhotos__box"
                     multiple={true}
                     listType="picture-card"
                     showUploadList={false}
@@ -105,10 +101,21 @@ export function EditPhotos({ photos, setPhotos }) {
                     <p className="ant-upload-drag-icon">
                         <InboxOutlined />
                     </p>
-                    <p className="ant-upload-text">Click or drag file to this area to upload</p>
+                    <p className="ant-upload-text">
+                        Click or drag file to this area to upload
+                    </p>
                 </Dragger>
             }
-            <></>
+            {loading &&
+                <Progress
+                    showInfo={false}
+                    percent={progress}
+                    strokeColor={{
+                        '0%': '#10136c',
+                        '50%': '#755596',
+                        '100%': '#fdc229',
+                    }}
+                />}
             <SortableList
                 shouldUseDragHandle={true}
                 useDragHandle

+ 58 - 0
src/components/uploadPhoto/EditTitlePost.js

@@ -0,0 +1,58 @@
+import { EditOutlined } from "@ant-design/icons"
+import { Button, Divider, Input } from "antd"
+import Text from "antd/lib/typography/Text"
+import Title from "antd/lib/typography/Title"
+import { useEffect, useState } from "react"
+
+
+
+
+export const EditTitlePost = ({ titleSend, setTitleSend }) => {
+
+    const [title, setTitle] = useState(titleSend || 'Enter title')
+    const [error, setError] = useState(false)
+    const [editMode, setEditMode] = useState(false)
+
+    useEffect(() => {
+        setTitle(titleSend || 'Enter title')
+    }, [titleSend]);
+
+    const addValueHandler = () => {
+        if (title.trim() !== '') {
+            setTitleSend(title.trim())
+            setEditMode(false)
+        } else {
+            setError(true)
+            setTitleSend('')
+        }
+    }
+
+    const onChangeInput = (e) => {
+        setTitle(e.currentTarget.value)
+        setError(false)
+    }
+
+    const onKeyPressAdd = (e) => e.charCode === 13 && addValueHandler()
+
+
+    return (
+        <>
+            <Divider orientation="left" orientationMargin="0">
+                <Title level={3}>Title
+                    <Button type="link" onClick={() => setEditMode(true)}><EditOutlined /></Button></Title>
+            </Divider>
+            {error && <Text type="danger">Field must not be empty</Text>}
+            {editMode
+                ? <Input
+                    value={title}
+                    placeholder="title"
+                    onChange={onChangeInput}
+                    autoFocus onBlur={() => addValueHandler()}
+                    onKeyPress={onKeyPressAdd}
+                />
+                : <Title level={5} onDoubleClick={() => setEditMode(true)}>
+                    {title}
+                </Title>
+            }
+        </>)
+}

+ 13 - 5
src/helpers/index.js

@@ -3,7 +3,13 @@ import { useEffect } from 'react';
 import { connect } from 'react-redux';
 import { Route } from 'react-router-dom';
 
+export const backURL = 'http://hipstagram.asmer.fs.a-level.com.ua'
 
+export const propsUploadFile = {
+    name: 'photo',
+    action: `${backURL}/upload`,
+    headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {}
+}
 
 export const jwtDecode = (token) => {
     try {
@@ -16,16 +22,17 @@ export const jwtDecode = (token) => {
     }
 }
 
-export const backURL = 'http://hipstagram.asmer.fs.a-level.com.ua'
-
 const getGQL = url =>
     async (query, variables = {}) => {
         let obj = await fetch(url, {
             method: 'POST',
             headers: {
                 "Content-Type": "application/json",
-                ...(localStorage.authToken ? { Authorization: 'Bearer ' + localStorage.authToken } :
-                    sessionStorage.authToken ? { Authorization: 'Bearer ' + sessionStorage.authToken } : {})
+                ...(localStorage.authToken
+                    ? { Authorization: 'Bearer ' + localStorage.authToken }
+                    : sessionStorage.authToken
+                        ? { Authorization: 'Bearer ' + sessionStorage.authToken }
+                        : {})
             },
             body: JSON.stringify({ query, variables })
         })
@@ -42,7 +49,8 @@ const CircularGallerySvg = () =>
         <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} />
+export const CircularGalleryIcon = props =>
+    <Icon component={CircularGallerySvg} {...props}/>
 
 const RRoute = ({ action, component: Component, ...routeProps }) => {
     const WrapperComponent = (componentProps) => {

+ 5 - 2
src/pages/AllPosts.jsx

@@ -1,7 +1,8 @@
 import React, { useEffect, useState } from 'react';
 import { connect } from 'react-redux';
 import { actionAllPosts, actionRemovePostsFeedAC } from '../actions';
-import { CPosts } from '../components/Posts';
+import { CPosts } from '../components/main/Posts';
+import { Container } from './Content';
 
 const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
     const [checkScroll, setCheckScroll] = useState(true)
@@ -28,7 +29,9 @@ const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
     }
 
     return (
-        <CPosts />
+        <Container>
+            <CPosts />
+        </Container>
     )
 }
 

+ 25 - 0
src/pages/Authorization.jsx

@@ -0,0 +1,25 @@
+import { Card, Col, Divider, Row } from "antd";
+import { NavLink } from "react-router-dom";
+import { CLoginForm, CRegisterForm } from "../components/FormAuthorization";
+import authBg from "../images/authBg.png"
+
+export const Authorization = ({ match: { params: { _id } } }) =>
+    <div className='Authorization' style={{ backgroundImage: `url(${authBg})` }}>
+        <Row justify="end" align="middle" className='Authorization__form'>
+            <Col >
+                <Card style={{ width: 380 }} >
+                    <NavLink activeClassName='active' to={`/auth/login`}><span>Log In</span></NavLink>
+                    <Divider type="vertical" />
+                    <NavLink activeClassName='active' to={'/auth/registration'}>Registration</NavLink>
+                    <Divider>
+                        {_id === 'login'
+                            ? 'Log in'
+                            : 'Registration'}
+                    </Divider >
+                    {_id === 'login'
+                        ? <CLoginForm buttonTitle={'Sign In'} />
+                        : <CRegisterForm buttonTitle={'Sign up'} />}
+                </Card>
+            </Col>
+        </Row >
+    </div>

+ 56 - 153
src/pages/EntityEditorPost.jsx

@@ -1,166 +1,69 @@
-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 { Button, Divider,  message } from "antd"
 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"
+import { actionFindPostOne, actionFullSentPost, actionRemovePostsFeedAC } from "../actions"
+import { EditPhotos } from "../components/uploadPhoto/EditPhotos"
+import { EditDescriptionPost } from "../components/uploadPhoto/EditDescriptionPost"
+import { EditTitlePost } from "../components/uploadPhoto/EditTitlePost"
+import { Container } from "./Content"
+import { history } from '../App'
 
 
 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()
+    <div className='ContainEditPost ContainerInner'>{children}</div>
+
+const EntityEditorPost = ({ match: { params: { _id } }, myID, entity, status, onSave, findPostOne, clearState }) => {
+
+    const [photos, setPhotos] = useState(entity?.images || []);
+    const [titleSend, setTitleSend] = useState(entity?.title || '')
+    const [description, setDescription] = useState(entity?.text || '');
+
+    useEffect(() => {
+        if (_id !== 'new')
+            findPostOne(_id)
+        return () => {
+            clearState()
+        };
+    }, []);
+
+    useEffect(() => {
+        setPhotos(entity?.images || [])
+        setTitleSend(entity?.title || '')
+        setDescription(entity?.text || '')
+    }, [entity]);
+
+    useEffect(() => {
+        if (status === "RESOLVED") {
+            message.success(`post published, can create a new one`)
+            history.push(`/profile/${myID}`)
         }
-    }
-    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 }
-    //     })
+    }, [status])
 
-    //     console.log(result)
+    const disabledBtn = photos.length && titleSend && description ? false : true
+    const sentPost = () => onSave(photos, titleSend, description)
 
-    // }
     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>
+        <Container>
+            <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>
+        </ Container>
     )
 }
 
-export const CEntityEditorPost = connect(null, { onSave: actionSentPost })(EntityEditorPost)
+export const CEntityEditorPost = connect(state => ({
+    myID: state?.auth?.payload?.sub?.id,
+    entity: state?.postsFeed?.posts,
+    status: state?.promise?.sentPost?.status
+}),
+    {
+        onSave: actionFullSentPost,
+        findPostOne: actionFindPostOne,
+        clearState: actionRemovePostsFeedAC,
+    })(EntityEditorPost)

+ 4 - 20
src/pages/Header.jsx

@@ -1,35 +1,19 @@
 import React from 'react'
 import logo from '../logo.svg';
-import noAva from '../images/noAva.png'
 import { Link } from 'react-router-dom';
 import { CFieldSearch } from '../components/header/Search';
 import { connect } from 'react-redux';
 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 {  Col, Menu, Popover, Row } from 'antd';
 import { UserOutlined, CompassOutlined, SettingOutlined, HomeOutlined, ImportOutlined, MessageOutlined, PlusCircleOutlined } from '@ant-design/icons/lib/icons';
+import { UserAvatar } from '../components/header/UserAvatar';
 
 const UserNav = () =>
     <div className='UserNav'>
         <CUserNavIcon />
     </div>
 
-export const UserAvatar = ({ avatarSize, avatar }) => {
-    return (
-        <>
-            <Avatar style={{
-                width: avatarSize,
-                height: avatarSize
-            }}
-                src={avatar && avatar?.url ?
-                    <img src={(backURL + '/' + avatar.url)} alt='avatar' /> :
-                    <img src={noAva} />
-                } />
-
-        </>
-    )
-}
 
 
 const ProfileDropMenu = ({ myID, onLogOut }) =>
@@ -38,7 +22,7 @@ const ProfileDropMenu = ({ myID, onLogOut }) =>
             <Link to={`/profile/${myID}`}><UserOutlined /> My Profile</Link>
         </Menu.Item>
         <Menu.Item key={'1'}>
-            <Link to={'/'}><SettingOutlined /> Settings</Link>
+            <Link to={'/my-settings'}><SettingOutlined /> Settings</Link>
         </Menu.Item>
         <Menu.Divider />
         <Menu.Item key={'2'}>
@@ -58,7 +42,7 @@ const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
             <Link to='/message'><MessageOutlined /></Link>
         </Col>
         <Col >
-            <Link to='/add'><PlusCircleOutlined /></Link>
+            <Link to='/edit/post/new'><PlusCircleOutlined /></Link>
         </Col>
         <Col >
             <Link to='/all'><CompassOutlined /></Link>

+ 14 - 21
src/pages/MainPostsFeed.jsx

@@ -2,21 +2,14 @@ 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>
+import { Container } from './Content'
+import { CPostTitle } from '../components/main/post/PostTitle'
 
 
 export const PostDescription = ({ title, description, date }) =>
@@ -31,22 +24,22 @@ export const PostDescription = ({ title, description, date }) =>
                 </Text>
             </Col>
         </Row>
-        <Paragraph ellipsis={true ? { rows: 1, expandable: true, symbol: '...' } : false}>
+        <Paragraph ellipsis={true ? { rows: 1, expandable: true, symbol: '...More' } : 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>
+    <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} />}
+            title={<CPostTitle owner={owner} postId={_id}/>}
             cover={<PostImage images={images} />}
         >
             <CPostUserPanel postId={_id} likes={likes} styleFontSize='1.7em' />
@@ -56,7 +49,7 @@ const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', com
     </div>
 
 
-const MainPostsFeed = ({ posts, postsFollowing, postsFollowingRemove, following }) => {
+const MainPostsFeed = ({ posts, postsFollowing, clearState, following }) => {
 
     const [checkScroll, setCheckScroll] = useState(true)
 
@@ -71,7 +64,7 @@ const MainPostsFeed = ({ posts, postsFollowing, postsFollowingRemove, following
         document.addEventListener('scroll', scrollHandler)
         return () => {
             document.removeEventListener('scroll', scrollHandler)
-            postsFollowingRemove()
+            clearState()
 
         }
     }, [])
@@ -83,9 +76,9 @@ const MainPostsFeed = ({ posts, postsFollowing, postsFollowingRemove, following
     }
 
     return (
-        <>
+        <Container>
             {Array.isArray(posts) && posts.map(p => <Post key={p._id} postData={p} />)}
-        </>
+        </Container>
     )
 }
 
@@ -94,5 +87,5 @@ export const CMainPostsFeed = connect(state => ({
     following: state?.myData.following || []
 }), {
     postsFollowing: actionPostsFeed,
-    postsFollowingRemove: actionRemovePostsFeedAC,
+    clearState: actionRemovePostsFeedAC,
 })(MainPostsFeed)

+ 8 - 7
src/pages/PostPage.jsx

@@ -2,22 +2,23 @@ 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 { 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';
+import { CPostTitle } from '../components/main/post/PostTitle';
+import { UserAvatar } from '../components/header/UserAvatar';
 
 
-const PostPageTitle = ({ data: { owner } }) =>
-    <PostTitle owner={owner} />
+const PostPageTitle = ({ data: { owner }, postId }) =>
+    <CPostTitle owner={owner} postId={postId} />
 
-const CPostPageTitle = connect(state => ({ data: state?.postsFeed?.posts || {} }))(PostPageTitle)
+const CPostPageTitle = connect(state => ({ data: state?.postsFeed?.posts || {}, postId: state?.postsFeed?.posts?._id }))(PostPageTitle)
 
 
 const PostCommentAuthor = ({ owner }) => {
@@ -87,9 +88,9 @@ const CPostComment = connect(state => ({
 }
 )(PostComment)
 
-const CPostSubComment = connect(state => ({ comments: state?.postsFeed?.SubComments }, {
+const CPostSubComment = connect(state => ({ comments: state?.postsFeed?.SubComments }), {
     findSubComment: actionSubComment,
-}))(PostComment)
+})(PostComment)
 
 const PostComments = ({ comments }) => {
     return (

+ 26 - 24
src/pages/ProfilePage.jsx

@@ -1,22 +1,19 @@
 import React, { useEffect, useState } from 'react'
-import { Button, Card, Col, Row } from 'antd'
+import { Button, 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 { actionProfilePageData, actionRemovePostsFeedAC, actionSubscribe, actionUnSubscribe } from '../actions'
 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'
+import { Container } from './Content'
+import { CPosts } from '../components/main/Posts'
+import { UserAvatar } from '../components/header/UserAvatar'
 
-
-
-
-
-const ProfileFollow = ({ myID, userId, followers, onSubsuscribe, onUnSubsuscribe }) => {
+const ProfileFollowButton = ({ myID, userId, followers, onSubsuscribe, onUnSubsuscribe }) => {
     const followCheck = followers.find(f => f._id === myID && true)
     return (
-        <Col className='Profile__seting' offset={4}>
+        <Col className='Profile__setting'>
             {!!followCheck ?
                 <Button onClick={() => onUnSubsuscribe(userId)}>UnSubscribe</Button> :
                 <Button onClick={() => onSubsuscribe(userId)} type="primary">Subscribe</Button>}
@@ -24,34 +21,36 @@ const ProfileFollow = ({ myID, userId, followers, onSubsuscribe, onUnSubsuscribe
     )
 }
 
-const CProfileSetting = connect(state => ({
+const CProfileFollowButton = connect(state => ({
     myID: state?.auth?.payload?.sub.id,
     followers: state?.postsFeed?.userData?.followers || []
-}), { onSubsuscribe: actionSubscribe, onUnSubsuscribe: actionUnSubscribe })(ProfileFollow)
+}), { onSubsuscribe: actionSubscribe, onUnSubsuscribe: actionUnSubscribe })(ProfileFollowButton)
 
 
-const ProfilePageData = ({ data: { _id, avatar, login, nick, createdAt = '', followers, following }, count, setFollowing, setFollowers }) =>
+const ProfilePageData = ({ myID, data: { _id, avatar, login, nick, createdAt = '', followers, following }, count, setFollowing, setFollowers }) =>
     <Row className='Profile' >
-        <Col span={8}>
+        <Col flex={'150px'}>
             <UserAvatar avatarSize={'150px'} avatar={avatar} />
         </Col>
-        <Col span={14} offset={1}>
-            <Row align='top' className='Profile__name'>
+        <Col className='Profile__data' flex={'auto'} offset={2}>
+            <Row align='top' justify='space-between' className='Profile__name'>
                 <Col>
                     <h1>{nick || login || 'No Name'}</h1>
                     <span className='Profile__login'>{login || '----'}</span>
                 </Col>
-                <Col>
-                    <CProfileSetting userId={_id} />
+                <Col >
+                    {myID !== _id
+                        ? <CProfileFollowButton userId={_id} />
+                        : <Button type=''><Link to={`/my-settings`}>Settings</Link></Button>}
                 </Col>
             </Row>
-            <Row align='middle'>
-                <Col >
+            <Row align='middle' justify='space-between'>
+                <Col className='Profile__created'>
                     <Text type='secondary'>Account created: <DateCreated date={createdAt} /></Text>
                 </Col>
-                <Col offset={2}>
+                {myID !== _id && <Col offset={1}>
                     <Link className='Profile__link-message' to='/message'>Send message</Link>
-                </Col>
+                </Col>}
             </Row>
             <Row className='Profile__count' align='middle' justify='space-between'>
                 <Col >
@@ -74,7 +73,9 @@ const ProfilePageData = ({ data: { _id, avatar, login, nick, createdAt = '', fol
         </Col >
     </Row >
 
+
 const CProfilePageData = connect(state => ({
+    myID: state.auth.payload.sub.id || '',
     data: state?.postsFeed?.userData || {},
     count: state?.postsFeed?.count || null
 }))(ProfilePageData)
@@ -87,6 +88,7 @@ const ProfilePage = ({ match: { params: { _id } }, getProfileUser, clearDataProf
     const [following, setFollowing] = useState(false)
     const [checkScroll, setCheckScroll] = useState(true)
 
+
     useEffect(() => {
         document.addEventListener('scroll', scrollHandler)
         return () => {
@@ -111,12 +113,12 @@ const ProfilePage = ({ match: { params: { _id } }, getProfileUser, clearDataProf
     }
 
     return (
-        <>
+        <Container>
             <CProfilePageData setFollowing={setFollowing} setFollowers={setFollowers} />
             {followers && < CModalFollowers statusModal={setFollowers} title={'Followers'} />}
             {following && < CModalFollowing statusModal={setFollowing} title={'Following'} />}
             <CPosts />
-        </>
+        </Container>
     )
 }
 

+ 129 - 0
src/pages/SettingsPage.jsx

@@ -0,0 +1,129 @@
+import React, { useEffect, useState } from 'react';
+import { Container } from './Content';
+import { CEditAvatar } from '../components/main/profilePage/EditAvatar'
+import { Button, Col, Divider, Input, message, Row } from 'antd'
+import Title from 'antd/lib/typography/Title';
+import { connect } from 'react-redux';
+import { EditOutlined } from '@ant-design/icons';
+import Text from 'antd/lib/typography/Text';
+import { actionFullAboutMeUpsert } from '../actions';
+
+
+const ContainerSettingsPage = ({ children }) =>
+    <div className="SettingsPage ContainerInner">{children}</div>
+
+
+const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
+    const [value, setValue] = useState(propValue);
+    const [editMode, setEditMode] = useState(false);
+    const [error, setError] = useState(false);
+
+    useEffect(() => {
+        setValue(propValue)
+    }, [propValue]);
+
+    const addValueHandler = () => {
+        const valid = /^[A-Z][a-z0-9_]{1,15}$/
+        if (valid.test(value)) {
+            propHandler(value)
+            setEditMode(false)
+            check(false)
+        } else {
+            setError(true)
+        }
+    }
+
+    const onChangeInput = (e) => {
+        setValue(e.currentTarget.value)
+        setError(false)
+    }
+
+    return (
+        <label onDoubleClick={() => setEditMode(true)}>
+            <Title level={4}>{title} :</Title>
+            <div className='EditMyData__lable-box'>
+                {error && <Text type='danger'> First letter is capitalized!!!</Text>}
+                {!editMode
+                    ? <Text className='EditMyData__lable-text'>{value}
+                        <EditOutlined
+                            onClick={() => setEditMode(true)}
+                            style={{ fontSize: '1.1em', color: '#1890ff ' }} />
+                    </Text>
+                    : <Input className={error && '--error'} value={value}
+                        onBlur={addValueHandler}
+                        onChange={onChangeInput}
+                        onPressEnter={addValueHandler}
+                        autoFocus />
+                }
+            </div>
+        </label>
+    )
+}
+
+const EditMyData = ({ myData, status, onUpsert }) => {
+
+    const [nick, setNick] = useState(myData?.nick || '');
+    const [login, setLogin] = useState(myData?.login || '');
+    const [checkEdit, setChekEdit] = useState(true);
+    useEffect(() => {
+        setNick(myData?.nick || '')
+        setLogin(myData?.login || '')
+    }, [myData])
+
+    useEffect(() => {
+        if (status === "RESOLVED") {
+            message.success({
+                content: 'User data updated',
+                className: 'custom-class',
+                style: {
+                    marginTop: '10vh',
+                },
+            });
+            setChekEdit(true)
+        } else if(status=== 'REJECTED'){
+            message.error({
+                content: 'Что-то не так, нужно повторить!',
+                className: 'custom-class',
+                style: {
+                    marginTop: '10vh',
+                },
+            });
+        }
+    }, [status])
+
+    const onSendEdit = () => onUpsert(nick, login)
+
+
+
+
+    return (
+        <>
+            <EditMyDataIput title='Nick' propValue={nick} propHandler={setNick} check={setChekEdit} />
+            <EditMyDataIput title='Login' propValue={login} propHandler={setLogin} check={setChekEdit} />
+            <Button type='primary'
+                disabled={nick && login ? false : true}
+                className={!checkEdit && '--block'}
+                onClick={onSendEdit}> SendEdit</Button>
+        </>
+    )
+}
+
+const CEditMyData = connect(state => ({ myData: state?.myData, status: state?.promise?.upsertAboutMe?.status }), { onUpsert: actionFullAboutMeUpsert })(EditMyData)
+
+export const SettingsPage = () => {
+    return (
+        <Container>
+            <ContainerSettingsPage>
+                <Divider><Title level={2}>Profile Settings</Title></Divider>
+                <Row >
+                    <Col flex={1}>
+                        <CEditAvatar />
+                    </Col>
+                    <Col flex={4} offset={1} className='EditMyData'>
+                        <CEditMyData />
+                    </Col>
+                </Row>
+            </ContainerSettingsPage>
+        </Container>
+    )
+};

+ 6 - 3
src/redux/reducers/postFeed-reducer.js

@@ -2,17 +2,19 @@ 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],
+                posts: Array.isArray(posts) === Array.isArray(newResult)
+                    ? [...posts, ...newResult]
+                    : { ...posts, ...newResult },
                 count
             }
         },
         'GET-POST': () => {
             return { ...state, posts: { ...newResult } }
-
         },
         'ADD-PROFILE-DATA': () => {
             return {
@@ -28,7 +30,8 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
                 posts: [],
                 userData: {},
                 count: 0,
-                subComments: {}
+                subComments: {},
+                editPost: {}
             }
         },
         'ADD-POST-LIKE': () => {

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

@@ -5,6 +5,11 @@ export function promiseReducer(state = {}, { type, status, payload, error, name
             ...state,
             [name]: { status, payload, error }
         }
+    } else if (type === 'CLEAR-PROMISE') {
+        return {
+            ...state,
+            [name]: {}
+        }
     }
     return state;
 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 44 - 5
src/redux/saga/index.js