Bläddra i källkod

22.03.2023 02:50

Volddemar4ik 1 år sedan
förälder
incheckning
3e21417a8a

+ 14 - 3
js/Project/project/src/components/feed/aboutMe.js

@@ -1,11 +1,17 @@
 import { Avatar, Typography, Box, Stack, CardHeader } from '@mui/material'
 import { useSelector, connect } from 'react-redux'
+import { useHistory } from 'react-router-dom'
 
 import { url } from "../../App"
 
 function AboutMe({ aboutMe = {} }) {
+    let history = useHistory()
 
-    const myPostsCount = useSelector(state => state?.promise?.MyPosts?.payload)
+    const myPostsCount = useSelector(state => state?.promise?.MyPostsCount?.payload)
+
+    function toMyAccount() {
+        history.push(`/user/${aboutMe?._id}`)
+    }
 
     return (
         <Box sx={{
@@ -21,7 +27,12 @@ function AboutMe({ aboutMe = {} }) {
                 }
 
                 subheader={
-                    <Typography variant='h6'>
+                    <Typography
+                        sx={{
+                            cursor: 'pointer'
+                        }}
+                        variant='h6'
+                        onClick={toMyAccount}>
                         {aboutMe?.login}
                     </Typography>
                 }
@@ -31,7 +42,7 @@ function AboutMe({ aboutMe = {} }) {
                 spacing={1}
                 padding={2}>
                 <Typography>
-                    {myPostsCount?.length || '0'} публикаций
+                    {myPostsCount || '0'} публикаций
                 </Typography>
                 <Typography>
                     {aboutMe?.followers?.length || '0'} подписчиков

+ 23 - 9
js/Project/project/src/components/feed/card_feed.js

@@ -1,20 +1,28 @@
 import { Card, CardHeader, CardMedia, CardContent, CardActions, Avatar, IconButton, Typography, Box, Divider } from '@mui/material'
 import { FavoriteBorderRounded, Send, ChatBubbleOutline, TurnedInNot } from '@mui/icons-material/'
 import Grid from '@mui/material/Unstable_Grid2';
-
-import { Link } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
 
 import { MyCarousel } from './carousel_feed';
 
 import { url } from '../../App';
 
 export function CardFeed({ postData }) {
+    const history = useHistory()
 
     // формируем дату поста
     const dateOfPost = new Date(+postData.createdAt)
     const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
     const dateOfPostParse = `${dateOfPost.getDate() < 10 ? '0' + dateOfPost.getDate() : dateOfPost.getDate()} ${months[dateOfPost.getMonth()]} ${dateOfPost.getFullYear()}  ${dateOfPost.getHours()}:${dateOfPost.getMinutes() < 10 ? '0' + dateOfPost.getMinutes() : dateOfPost.getMinutes()}`
 
+    function toUser() {
+        history.push(`/user/${postData?.owner?._id}`)
+    }
+
+    function toPost() {
+        history.push(`/post/${postData?._id}`)
+    }
+
     return (
         <Card sx={{
             maxWidth: 600
@@ -36,11 +44,14 @@ export function CardFeed({ postData }) {
                 // }
 
                 title={
-                    <Link
-                        to={`/user/${postData?.owner?._id}`}
+                    <Typography
+                        sx={{
+                            cursor: 'pointer'
+                        }}
+                        onClick={toUser}
                     >
                         {postData?.owner?.login}
-                    </Link>
+                    </Typography>
                 }
 
                 subheader={dateOfPostParse}
@@ -118,11 +129,14 @@ export function CardFeed({ postData }) {
             </CardContent>
 
             <CardActions>
-                <Link
-                    to={`/post/${postData._id}`}
-                >
+                <Typography
+                    sx={{
+                        cursor: 'pointer'
+                    }}
+                    onClick={toPost}>
                     Посмотреть все комментарии ({postData?.comments?.length || '0'})
-                </Link>
+                </Typography>
+
             </CardActions>
 
             <Divider />

+ 32 - 30
js/Project/project/src/components/feed/index.js

@@ -1,18 +1,16 @@
-import * as React from 'react';
-import { useEffect } from 'react';
-import { connect } from 'react-redux';
+import React, { useEffect, useState } from 'react';
+import { connect, useDispatch, useSelector } from 'react-redux';
 
 import { Box, Paper } from '@mui/material';
 import { styled } from '@mui/material/styles';
 import { Container } from '@mui/system';
 import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
 
-import { actionAboutMe } from '../redux/action';
+import { actionAboutMe, actionDownloadFeed } from '../redux/action';
 import { CardFeed } from './card_feed';
 import { CAboutMe } from './aboutMe';
 
 
-// сам item для поста
 const Item = styled(Paper)(() => ({
     padding: '0 10px',
     marginBottom: 10,
@@ -20,10 +18,36 @@ const Item = styled(Paper)(() => ({
     boxShadow: 'none',
 }))
 
-// function Feed({ feed, me = {} }) { // это первый вариант без useEffetc, а просто с store.dispatch
+
 function Feed({ feed, loadFeed }) {
+    const dispatch = useDispatch()
+
+    const [posts, setPosts] = useState(feed)
+
+    const newPosts = useSelector(state => state.feed?.AddFeed?.payload)
+
+    useEffect((() => {
+        if (newPosts) {
+            setPosts([...posts, ...newPosts])
+        }
+    }), [newPosts])
+
+    window.onscroll = async function feedScroll() {
+        if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
+            const downloadedPosts = await dispatch(actionDownloadFeed(feed.length))
+
+            // добавляем новые посты в общй массив постов
+            downloadedPosts.map(post => feed.push(post))
+        }
+    }
+
 
-    useEffect(() => { loadFeed() }, []) // это второй вариант через useEffect
+    // console.log('feed: ', feed)
+    // console.log('posts: ', posts)
+
+
+
+    useEffect(() => { loadFeed() }, [])
 
     return (
         <Container sx={{
@@ -50,26 +74,4 @@ function Feed({ feed, loadFeed }) {
     )
 }
 
-export const CFeed = connect(state => ({ feed: state.promise?.MyFeed?.payload }), { loadFeed: actionAboutMe })(Feed)
-
-
-
-// store.dispatch(actionAboutMe()) // это первый вариант без useEffetc, а просто с store.dispatch
-// export const CFeed = connect(state => ({ feed: state.promise?.MyFeed?.payload }))(Feed) // это первый вариант без useEffetc, а просто с store.dispatch
-
-// const CMe = connect(state => ({ me: state?.promise?.AboutMe?.payload }))(AboutMe) // это первый вариант когда все здесь находится
-
-
-
-
-
-
-
-
-
-
-
-
-// загрузка ленты всех постов из базы
-// store.dispatch(actionfindPosts())
-// export const ReduxFeed = connect(state => ({ feed: state?.promise?.PostsFind?.payload }))(Feed)
+export const CFeed = connect(state => ({ feed: state.feed?.MyFeed?.payload }), { loadFeed: actionAboutMe })(Feed)

+ 133 - 0
js/Project/project/src/components/feed/test.js

@@ -0,0 +1,133 @@
+import * as React from 'react';
+import { useEffect } from 'react';
+import { connect, useDispatch } from 'react-redux';
+import { store } from '../redux';
+import { useState } from 'react';
+import { useSelector } from 'react-redux';
+
+import { Box, Paper } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import { Container } from '@mui/system';
+import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
+
+import { actionAboutMe, actionDownloadFeed } from '../redux/action';
+import { CardFeed } from './card_feed';
+import { CAboutMe } from './aboutMe';
+
+
+
+
+// // проверка скролла до конца страницы
+// window.onscroll = function (ev) {
+//     if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
+//         console.log('долистал:  ', (window.innerHeight + window.scrollY), ' ', document.body.offsetHeight)
+
+
+//     }
+// };
+
+
+
+
+
+// сам item для поста
+const Item = styled(Paper)(() => ({
+    padding: '0 10px',
+    marginBottom: 10,
+    borderRadius: 0,
+    boxShadow: 'none',
+}))
+
+// function Feed({ feed, me = {} }) { // это первый вариант без useEffetc, а просто с store.dispatch
+function Feed({ feed, loadFeed }) {
+
+    const dispatch = useDispatch()
+    const [posts, setPosts] = useState(feed)
+    const newPosts = useSelector(state => state.feed?.AddFeed?.payload)
+
+    useEffect((() => {
+        console.log('newPosts: ', newPosts)
+        if (newPosts) {
+            setPosts({ ...posts, newPosts }) // вот этот вариант работает!!!
+        }
+    }), [newPosts])
+
+
+    // проверка скролла до конца страницы
+    window.onscroll = async function () {
+        if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
+            // const downloadedPosts = await store.dispatch(actionDownloadFeed(feed.length))
+            const downloadedPosts = await dispatch(actionDownloadFeed(feed.length))
+
+            // добавляем новые посты в общй массив постов
+            downloadedPosts.map(post => feed.push(post))
+        }
+    }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    useEffect(() => { loadFeed() }, []) // это второй вариант через useEffect
+
+    return (
+        <Container sx={{
+            width: '90%',
+            mt: 1
+        }}>
+            <Box sx={{ flexGrow: 1 }}>
+                <Grid2 container spacing={2}>
+                    <Grid2 xs={7}>
+                        <Item>
+                            {feed && feed.map(post => <Item key={post._id}><CardFeed postData={post} /></Item>)}
+                        </Item>
+                    </Grid2>
+                    <Grid2 xs={5}>
+                        <Item sx={{
+                            position: 'fixed'
+                        }}>
+                            <CAboutMe />
+                        </Item>
+                    </Grid2>
+                </Grid2>
+            </Box>
+        </Container>
+    )
+}
+
+// export const CFeed = connect(state => ({ feed: state.promise?.MyFeed?.payload }), { loadFeed: actionAboutMe })(Feed)
+export const CFeed = connect(state => ({ feed: state.feed?.MyFeed?.payload }), { loadFeed: actionAboutMe })(Feed)
+
+
+
+// store.dispatch(actionAboutMe()) // это первый вариант без useEffetc, а просто с store.dispatch
+// export const CFeed = connect(state => ({ feed: state.promise?.MyFeed?.payload }))(Feed) // это первый вариант без useEffetc, а просто с store.dispatch
+
+// const CMe = connect(state => ({ me: state?.promise?.AboutMe?.payload }))(AboutMe) // это первый вариант когда все здесь находится
+
+
+
+
+
+
+
+
+
+
+
+
+// загрузка ленты всех постов из базы
+// store.dispatch(actionfindPosts())
+// export const ReduxFeed = connect(state => ({ feed: state?.promise?.PostsFind?.payload }))(Feed)

+ 425 - 0
js/Project/project/src/components/redux/action(test).js

@@ -0,0 +1,425 @@
+import { store } from "."
+import { useGetState } from 'react-redux';
+import { useHistory } from "react-router-dom";
+
+const url = 'http://hipstagram.node.ed.asmer.org.ua/graphql'
+
+// функция getGql
+function getGql(endpoint) {
+  return async function gql(query, variables = {}) {
+
+    let headers = {
+      'Content-Type': 'application/json;charset=utf-8',
+      'Accept': 'application/json',
+    }
+    if (('authToken' in localStorage)) {
+      headers.Authorization = 'Bearer ' + localStorage.authToken
+    }
+
+    let result = await fetch(endpoint, {
+      method: 'POST',
+      headers,
+      body: JSON.stringify({
+        query,
+        variables
+      })
+    }).then(res => res.json())
+
+    if (('errors' in result) && !('data' in result)) {
+      throw new Error(JSON.stringify(result.errors))
+    }
+
+    result = Object.values(result.data)[0]
+
+    return result
+  }
+}
+
+const gql = getGql(url)
+
+// // акшоны для promiseReducer
+export const actionPending = nameOfPromise => ({ nameOfPromise, type: 'PROMISE', status: 'PENDING' })
+export const actionFulfilled = (nameOfPromise, payload) => ({ nameOfPromise, type: 'PROMISE', status: 'FULFILLED', payload })
+export const actionRejected = (nameOfPromise, error) => ({ nameOfPromise, type: 'PROMISE', status: 'REJECTED', error })
+
+export const actionPromise = (nameOfPromise, promise) =>
+  async dispatch => {
+    dispatch(actionPending(nameOfPromise)) //сигнализируем redux, что промис начался
+    try {
+      const payload = await promise //ожидаем промиса
+      dispatch(actionFulfilled(nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен
+      return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
+    }
+    catch (error) {
+      dispatch(actionRejected(nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился
+    }
+  }
+
+
+
+// =============================================================
+// Запросы на бек
+
+// Запрос на регистрацию пользователя
+export const actionRegistration = (login, password) => actionPromise('Registration', gql(`mutation Registration($login:String!, $password:String!) {
+   createUser(login:$login, password:$password) {
+    _id login
+  }
+}`, {
+  login,
+  password
+})
+
+)
+
+
+
+// Запрос на логинизацию пользователя
+export const actionLogin = (login, password) => actionPromise('Login', gql(`query Login($login: String!, $password: String!) {
+login(login: $login, password: $password)
+}`, {
+  login,
+  password
+}))
+
+
+
+
+// акшон для логинизации в authReduser
+const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
+// акшон для раззлогинивания
+export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
+
+
+
+
+//  запрос на изменение пароля юзера
+export const actionPassChange = (loginChange, passwordChange, passwordNew) => actionPromise('PassChange', gql(`mutation PasswordChange($loginChange: String!, $passwordChange: String!, $passwordNew: String!) {
+  changePassword(
+    login: $loginChange, password: $passwordChange, newPassword: $passwordNew
+  ) {
+       _id login
+
+  }
+}`, {
+  loginChange,
+  passwordChange,
+  passwordNew
+}))
+
+
+
+
+// запрос на поиск конкретного поста
+export const actionFindPostOne = _id => actionPromise('PostFindOne', gql(`query OnePostFind ($postOne: String){
+  PostFindOne (query: $postOne) {
+    _id createdAt title text likesCount
+    images {
+      url
+    }
+    comments {
+      _id createdAt text answers {
+        _id createdAt text likes {
+          _id
+        }
+        likesCount owner {
+          _id login nick  avatar {
+            _id url
+          }
+        }
+      }
+      likesCount
+    }
+    owner {
+      _id login nick avatar {
+        _id url
+      }
+    }
+    
+  }
+}`, {
+  postOne: JSON.stringify([{ _id }])
+}))
+
+// запрос на поиск конкретного юзера(folowwing - это те, на кого я подписан. followers - те, кто на меня подписан)
+// export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise(promiseName, gql(`query OneUserFind ($userOne: String) {
+//     UserFindOne(query: $userOne) {
+//       _id createdAt login nick
+//       avatar {
+//         _id url
+//       }
+//        followers {
+//         _id login nick avatar {_id url}
+//       }
+//       following {
+//          _id login nick avatar {_id url}
+//       }
+//     }
+//   }`, {
+//   userOne: JSON.stringify([{ _id }])
+// }))
+
+
+
+export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise(promiseName, gql(`query OneUserFind ($userOne: String) {
+    UserFindOne(query: $userOne) {
+      _id createdAt login nick 
+      avatar {
+        _id url
+      } 
+       followers {
+        _id login nick avatar {_id url}
+      }
+      following {
+         _id
+      }
+    }
+  }`, {
+  userOne: JSON.stringify([{ _id }])
+}))
+
+
+
+// запрос на поиск всех постов на беке (нигде не должен использоваться)
+export const actionfindPosts = () => actionPromise('PostsFind', gql(`query AllPostsFind ($allPosts: String){
+  PostFind(query: $allPosts){
+    _id createdAt title text likesCount
+    images {
+      url
+    }
+    owner {
+      _id login nick
+    }
+    
+  }
+}`, {
+  allPosts: JSON.stringify([{}])
+}))
+
+
+
+// Запрос на поиск ленты постов для юзера
+export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed') => actionPromise(promiseName, gql(`query FeedFindOne ($feedOne: String){
+	PostFind(query: $feedOne){
+            _id createdAt title text likesCount owner{
+              _id login avatar{
+                url
+              }
+            }
+            comments {
+              _id
+            }
+    images{
+      url
+    }
+    	}
+    }`, {
+  // вот тут прописал limit - количество постов, которые отображаются в ленте
+  feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }], limit: [limitOne] }])
+}))
+
+
+
+
+
+
+
+
+// запрос ленты с п/ропуском определенного количества постов.комментов
+export const actionFeedFindOneSkip = (arr, sortOne, skipOne, limitOne, promiseName = 'Feed') => actionPromise(promiseName, gql(`query FeedFindOne ($feedOne: String){
+	PostFind(query: $feedOne){
+            _id createdAt title text likesCount owner{
+              _id login avatar{
+                url
+              }
+            }
+            comments {
+              _id
+            }
+    images{
+      url
+    }
+    	}
+    }`, {
+  // вот тут прописал limit - количество постов, которые отображаются в ленте
+  feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }], skip: [skipOne], limit: [limitOne] }])
+}))
+
+
+
+
+
+
+
+
+
+
+
+
+
+// запрос на подсчет количества постов юзера
+export const actionPostsCount = (id, promiseName) => actionPromise(promiseName, gql(`query PostsCount ($postsCount: String){
+  PostCount(query: $postsCount)
+}`, {
+  postsCount: JSON.stringify([{ ___owner: id }])
+})
+
+)
+
+
+
+
+// =========================================
+// Thunk-и
+
+// Thunk логин и последующую логинизацию в authReduser
+export const actionFullLogin = (login, password) =>
+  async dispatch => {
+
+    const token = await dispatch(actionLogin(login, password))
+
+    if (token !== null) {
+      if (typeof (token) === 'string') {
+        dispatch(actionAuthLogin(token))
+        localStorage.authToken = token
+      }
+    }
+
+    return token
+  }
+
+
+
+
+
+
+// Thunk на регистрацию и последующую логинизацию
+export const actionFullRegistration = (login, password) =>
+  async dispatch => {
+
+    const token = await dispatch(actionRegistration(login, password))
+
+    if (token !== null) {
+      dispatch(actionFullLogin(login, password))
+    }
+
+    return token
+  }
+
+
+
+
+
+// Thunk разлогина и последующей полной очистки localStorage
+export const actionFullLogout = () =>
+  async dispatch => {
+    await dispatch(actionAuthLogout())
+    return {}
+  }
+
+
+
+
+
+// Thunk для страницы юзера (диспатчим запрос на юзера и на ленту его постов)
+export const actionFullUserFindOne = (id) =>
+  async dispatch => {
+    const test = await dispatch(actionFindUserOne(id, 'UserFind'))
+    console.log('test: ', test)
+    dispatch(actionFeedFindOne([id], -1, 100, 'UserFeed'))
+  }
+
+
+
+
+
+
+// запрос AboutMe для главной страницы (лента моих постов и мои данные)
+export const actionAboutMe = () =>
+  async (dispatch, getState) => {
+    const myId = getState()?.auth?.payload?.sub?.id
+
+    // диспатчим запрос AboutMe (о себе)
+    const userData = await dispatch(actionFindUserOne(myId, 'AboutMe'))
+
+    // парсим счетчик, сколько постов у меня
+    dispatch(actionPostsCount(myId, 'MyPostsCount'))
+
+    let followingList = []
+    // проверяем, есть ли вообще подписчики и если да, парсим их
+    if (userData?.following) {
+      // получаем id всех подписок
+      followingList = (userData.following).map(user => user?._id)
+    }
+
+    // запрашиваем формирование ленты моих постов (первый параметр - список id, второй - это сортировка постов от новых)
+    dispatch(actionFeedFindOne(followingList, -1, 10, 'MyFeed'))
+  }
+
+
+
+
+
+// запрос на загрузку картинок на бек
+function fileUpload(file) {
+  const formData = new FormData()
+  formData.append('photo', file)
+
+  return (
+    fetch('http://hipstagram.node.ed.asmer.org.ua/upload', {
+      method: 'POST',
+      headers: {
+        Authorization: 'Bearer ' + localStorage.authToken
+      },
+      body: formData
+    }).then(res => res.json())
+  )
+}
+
+function filesUpload(files) {
+  return Promise.all(files.map(fileUpload))
+}
+
+export const actionFilesUpload = (files) => actionPromise('FilesUpload',
+  filesUpload(files)
+)
+
+
+
+
+
+
+
+// запрос на создание поста
+export const actionCreatePost = (params) => actionPromise('CreatePost', gql(
+  `mutation CreatePost($createNewPost: PostInput){
+  PostUpsert(post: $createNewPost){
+    _id
+  }
+}`, {
+  createNewPost: params
+}))
+
+
+
+
+
+
+
+
+
+// санк для создания поста и последующего перехода на его страницу
+export const actionFullCreatePost = (params) =>
+  async dispatch => {
+    const newPost = await dispatch(actionCreatePost(params))
+    // console.log('Тут нужный резульата: ', newPost)
+
+    if (newPost) {
+      dispatch(actionFindPostOne(newPost._id))
+    }
+  }
+
+
+
+
+

+ 178 - 29
js/Project/project/src/components/redux/action.js

@@ -38,32 +38,31 @@ function getGql(endpoint) {
 const gql = getGql(url)
 
 // акшоны для promiseReducer
-export const actionPending = nameOfPromise => ({ nameOfPromise, type: 'PROMISE', status: 'PENDING' })
-export const actionFulfilled = (nameOfPromise, payload) => ({ nameOfPromise, type: 'PROMISE', status: 'FULFILLED', payload })
-export const actionRejected = (nameOfPromise, error) => ({ nameOfPromise, type: 'PROMISE', status: 'REJECTED', error })
+export const actionPending = (type, nameOfPromise) => ({ nameOfPromise, type, status: 'PENDING' })
+export const actionFulfilled = (type, nameOfPromise, payload) => ({ nameOfPromise, type: type, status: 'FULFILLED', payload })
+export const actionRejected = (type, nameOfPromise, error) => ({ nameOfPromise, type: type, status: 'REJECTED', error })
 
-export const actionPromise = (nameOfPromise, promise) =>
+export const actionPromise = (type, nameOfPromise, promise) =>
   async dispatch => {
-    dispatch(actionPending(nameOfPromise)) //сигнализируем redux, что промис начался
+    dispatch(actionPending(type, nameOfPromise)) //сигнализируем redux, что промис начался
     try {
       const payload = await promise //ожидаем промиса
-      dispatch(actionFulfilled(nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен
+      dispatch(actionFulfilled(type, nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен
       return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
     }
     catch (error) {
-      dispatch(actionRejected(nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился
+      dispatch(actionRejected(type, nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился
     }
   }
 
 
 
 
-
 // =============================================================
 // Запросы на бек
 
 // Запрос на регистрацию пользователя
-export const actionRegistration = (login, password) => actionPromise('Registration', gql(`mutation Registration($login:String!, $password:String!) {
+export const actionRegistration = (login, password) => actionPromise('PROMISE', 'Registration', gql(`mutation Registration($login:String!, $password:String!) {
    createUser(login:$login, password:$password) {
     _id login
   }
@@ -74,8 +73,10 @@ export const actionRegistration = (login, password) => actionPromise('Registrati
 
 )
 
+
+
 // Запрос на логинизацию пользователя
-export const actionLogin = (login, password) => actionPromise('Login', gql(`query Login($login: String!, $password: String!) {
+export const actionLogin = (login, password) => actionPromise('PROMISE', 'Login', gql(`query Login($login: String!, $password: String!) {
 login(login: $login, password: $password)
 }`, {
   login,
@@ -83,14 +84,18 @@ login(login: $login, password: $password)
 }))
 
 
+
+
 // акшон для логинизации в authReduser
 const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
 // акшон для раззлогинивания
 export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
 
 
+
+
 //  запрос на изменение пароля юзера
-export const actionPassChange = (loginChange, passwordChange, passwordNew) => actionPromise('PassChange', gql(`mutation PasswordChange($loginChange: String!, $passwordChange: String!, $passwordNew: String!) {
+export const actionPassChange = (loginChange, passwordChange, passwordNew) => actionPromise('PROMISE', 'PassChange', gql(`mutation PasswordChange($loginChange: String!, $passwordChange: String!, $passwordNew: String!) {
   changePassword(
     login: $loginChange, password: $passwordChange, newPassword: $passwordNew
   ) {
@@ -104,8 +109,10 @@ export const actionPassChange = (loginChange, passwordChange, passwordNew) => ac
 }))
 
 
+
+
 // запрос на поиск конкретного поста
-export const actionFindPostOne = _id => actionPromise('PostFindOne', gql(`query OnePostFind ($postOne: String){
+export const actionFindPostOne = _id => actionPromise('PROMISE', 'PostFindOne', gql(`query OnePostFind ($postOne: String){
   PostFindOne (query: $postOne) {
     _id createdAt title text likesCount
     images {
@@ -136,7 +143,7 @@ export const actionFindPostOne = _id => actionPromise('PostFindOne', gql(`query
 }))
 
 // запрос на поиск конкретного юзера(folowwing - это те, на кого я подписан. followers - те, кто на меня подписан)
-// export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise(promiseName, gql(`query OneUserFind ($userOne: String) {
+// export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise('PROMISE', promiseName, gql(`query OneUserFind ($userOne: String) {
 //     UserFindOne(query: $userOne) {
 //       _id createdAt login nick
 //       avatar {
@@ -152,7 +159,10 @@ export const actionFindPostOne = _id => actionPromise('PostFindOne', gql(`query
 //   }`, {
 //   userOne: JSON.stringify([{ _id }])
 // }))
-export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise(promiseName, gql(`query OneUserFind ($userOne: String) {
+
+
+
+export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise('PROMISE', promiseName, gql(`query OneUserFind ($userOne: String) {
     UserFindOne(query: $userOne) {
       _id createdAt login nick 
       avatar {
@@ -169,8 +179,10 @@ export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPro
   userOne: JSON.stringify([{ _id }])
 }))
 
+
+
 // запрос на поиск всех постов на беке (нигде не должен использоваться)
-export const actionfindPosts = () => actionPromise('PostsFind', gql(`query AllPostsFind ($allPosts: String){
+export const actionfindPosts = () => actionPromise('PROMISE', 'PostsFind', gql(`query AllPostsFind ($allPosts: String){
   PostFind(query: $allPosts){
     _id createdAt title text likesCount
     images {
@@ -185,8 +197,30 @@ export const actionfindPosts = () => actionPromise('PostsFind', gql(`query AllPo
   allPosts: JSON.stringify([{}])
 }))
 
+
+
 // Запрос на поиск ленты постов для юзера
-export const actionFeedFindOne = (arr, sortOne, promiseName = 'Feed') => actionPromise(promiseName, gql(`query FeedFindOne ($feedOne: String){
+// export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed') => actionPromise('PROMISE', promiseName, gql(`query FeedFindOne ($feedOne: String){
+// 	PostFind(query: $feedOne){
+//             _id createdAt title text likesCount owner{
+//               _id login avatar{
+//                 url
+//               }
+//             }
+//             comments {
+//               _id
+//             }
+//     images{
+//       url
+//     }
+//     	}
+//     }`, {
+//   // вот тут прописал limit - количество постов, которые отображаются в ленте
+//   feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }], limit: [limitOne] }])
+// }))
+
+//================= вот это тоже нужно будет удалить после тестов =====================
+export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed', skipOne = 0) => actionPromise('FEED', promiseName, gql(`query FeedFindOne ($feedOne: String){
 	PostFind(query: $feedOne){
             _id createdAt title text likesCount owner{
               _id login avatar{
@@ -201,16 +235,82 @@ export const actionFeedFindOne = (arr, sortOne, promiseName = 'Feed') => actionP
     }
     	}
     }`, {
-  feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }] }])
+  // вот тут прописал limit - количество постов, которые отображаются в ленте
+  feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }], limit: [limitOne], skip: [skipOne] }])
 }))
 
 
 
 
+
+
+
+// запрос ленты с пропуском определенного количества постов.комментов
+// export const actionFeedFindOneSkip = (arr, sortOne, skipOne, limitOne, promiseName = 'Feed') => actionPromise('FEED', promiseName, gql(`query FeedFindOne ($feedOne: String){
+// 	PostFind(query: $feedOne){
+//             _id createdAt title text likesCount owner{
+//               _id login avatar{
+//                 url
+//               }
+//             }
+//             comments {
+//               _id
+//             }
+//     images{
+//       url
+//     }
+//     	}
+//     }`, {
+//   // вот тут прописал limit - количество постов, которые отображаются в ленте
+//   feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }], skip: [skipOne], limit: [limitOne] }])
+// }))
+
+// ====================== и это удалить после теста ====================
+export const actionFeedFindOneSkip = (arr, sortOne, skipOne, limitOne, promiseName = 'Feed') => actionPromise('FEED', promiseName, gql(`query FeedFindOne ($feedOne: String){
+	PostFind(query: $feedOne){
+            _id createdAt title text likesCount owner{
+              _id login avatar{
+                url
+              }
+            }
+            comments {
+              _id
+            }
+    images{
+      url
+    }
+    	}
+    }`, {
+  // вот тут прописал limit - количество постов, которые отображаются в ленте
+  feedOne: JSON.stringify([{ ___owner: { $in: arr } }, { sort: [{ _id: sortOne }], skip: [skipOne], limit: [limitOne] }])
+}))
+
+
+
+
+
+
+
+
+
+
+
+
+// запрос на подсчет количества постов юзера
+export const actionPostsCount = (id, promiseName) => actionPromise('PROMISE', promiseName, gql(`query PostsCount ($postsCount: String){
+  PostCount(query: $postsCount)
+}`, {
+  postsCount: JSON.stringify([{ ___owner: id }])
+})
+
+)
+
+
+
+
 // =========================================
 // Thunk-и
 
-
 // Thunk логин и последующую логинизацию в authReduser
 export const actionFullLogin = (login, password) =>
   async dispatch => {
@@ -228,6 +328,10 @@ export const actionFullLogin = (login, password) =>
   }
 
 
+
+
+
+
 // Thunk на регистрацию и последующую логинизацию
 export const actionFullRegistration = (login, password) =>
   async dispatch => {
@@ -253,15 +357,28 @@ export const actionFullLogout = () =>
   }
 
 
+
+
+
 // Thunk для страницы юзера (диспатчим запрос на юзера и на ленту его постов)
-export const actionFullUserFindOne = (id) =>
+export const actionFullUserFindOne = _id =>
   async dispatch => {
-    const test = await dispatch(actionFindUserOne(id, 'UserFind'))
-    console.log('test: ', test)
-    dispatch(actionFeedFindOne([id], -1, 'UserFeed'))
+
+    // запрашиваем информацию о пользователе
+    await dispatch(actionFindUserOne(_id, 'UserFindOne'))
+
+    // парсим счетчик, сколько постов у юзера
+    dispatch(actionPostsCount(_id, 'UserPostsCount'))
+
+    // запрашиваем список постов для юзера
+    dispatch(actionFeedFindOne([_id], -1, 100, 'UserFeed'))
   }
 
 
+
+
+
+
 // запрос AboutMe для главной страницы (лента моих постов и мои данные)
 export const actionAboutMe = () =>
   async (dispatch, getState) => {
@@ -270,21 +387,41 @@ export const actionAboutMe = () =>
     // диспатчим запрос AboutMe (о себе)
     const userData = await dispatch(actionFindUserOne(myId, 'AboutMe'))
 
-    // парсим все мои посты
-    dispatch(actionFeedFindOne([myId], -1, 'MyPosts'))
+    // парсим счетчик, сколько постов у меня
+    dispatch(actionPostsCount(myId, 'MyPostsCount'))
 
+    let followingList = []
     // проверяем, есть ли вообще подписчики и если да, парсим их
     if (userData?.following) {
       // получаем id всех подписок
-      const followingList = (userData.following).map(user => user?._id)
+      followingList = (userData.following).map(user => user?._id)
+    }
+
+    // запрашиваем формирование ленты моих постов (первый параметр - список id, второй - это сортировка постов от новых)
+    dispatch(actionFeedFindOne(followingList, -1, 10, 'MyFeed'))
+  }
+
+
 
-      // запрашиваем формирование ленты моих постов (первый параметр - список id, второй - это сортировка постов от новых)
-      dispatch(actionFeedFindOne(followingList, -1, 'MyFeed'))
 
+// ================= на удаление (начало)=====================
+
+export const actionDownloadFeed = (skip) =>
+  async (dispatch, getState) => {
+
+    console.log('скипаем: ', skip)
+    const followingList = (getState()?.promise?.AboutMe?.payload?.following).map(user => user._id)
+
+    if (followingList) {
+      const result = await dispatch(actionFeedFindOne(followingList, -1, 10, 'AddFeed', skip))
+
+      return result
     }
   }
 
 
+// ================= на удаление(конец) =====================
+
 
 
 // запрос на загрузку картинок на бек
@@ -307,14 +444,18 @@ function filesUpload(files) {
   return Promise.all(files.map(fileUpload))
 }
 
-export const actionFilesUpload = (files) => actionPromise('FilesUpload',
+export const actionFilesUpload = (files) => actionPromise('PROMISE', 'FilesUpload',
   filesUpload(files)
 )
 
 
 
+
+
+
+
 // запрос на создание поста
-export const actionCreatePost = (params) => actionPromise('CreatePost', gql(
+export const actionCreatePost = (params) => actionPromise('PROMISE', 'CreatePost', gql(
   `mutation CreatePost($createNewPost: PostInput){
   PostUpsert(post: $createNewPost){
     _id
@@ -323,6 +464,14 @@ export const actionCreatePost = (params) => actionPromise('CreatePost', gql(
   createNewPost: params
 }))
 
+
+
+
+
+
+
+
+
 // санк для создания поста и последующего перехода на его страницу
 export const actionFullCreatePost = (params) =>
   async dispatch => {

+ 14 - 1
js/Project/project/src/components/redux/reducers.js

@@ -53,6 +53,7 @@ export function authReducer(state = {}, { type, token }) {
     if (type === 'AUTH_LOGOUT') {
         localStorage.clear()
         return {}
+
     }
 
     return state
@@ -60,6 +61,18 @@ export function authReducer(state = {}, { type, token }) {
 
 
 
+// feedReducer
+export function feedReducer(state = {}, { type, status, payload, error, nameOfPromise }) {
+    if (type === 'FEED') {
+        return {
+            ...state,
+            [nameOfPromise]: { status, payload, error }
+        }
+    }
+    return state
+}
+
+
 
 // localStoredReducer, который обрабатывает все наши редьюсеры для разных направлений
 export function localStoredReducer(originalReducer, localStorageKey) {
@@ -85,7 +98,7 @@ export function localStoredReducer(originalReducer, localStorageKey) {
 export const reducers = {
     promise: localStoredReducer(promiseReducer, 'promise'),
     auth: localStoredReducer(authReducer, 'auth'),
-    // feed: localStoredReducer(feedReducer, 'feed')
+    feed: localStoredReducer(feedReducer, 'feed')
 }
 
 // скармливаем объект с редьюсерами в combineReducers

js/Project/project/src/components/user/gallery2.js → js/Project/project/src/components/user/gallery(test).js


+ 51 - 131
js/Project/project/src/components/user/gallery.js

@@ -1,158 +1,78 @@
 import * as React from 'react';
+import { connect } from 'react-redux';
+import { useHistory } from 'react-router-dom';
 
-import ImageList from '@mui/material/ImageList';
-import ImageListItem from '@mui/material/ImageListItem';
 import { Box } from '@mui/system';
+import { ImageList, ImageListItem } from '@mui/material';
 
-import AutoAwesomeMotionRoundedIcon from '@mui/icons-material/AutoAwesomeMotionRounded';
+import { AutoAwesomeMotionRounded } from '@mui/icons-material';
 
-export default function StandardImageList() {
+import { url } from '../../App';
 
-    const clicker = (name) => {
-        console.log('click: ', name)
+
+export default function StandardImageList({ images }) {
+
+    const history = useHistory()
+
+    // переход на пост при клике на его картинку
+    function toPost(name) {
+        history.push(`/post/${name}`)
     }
 
-    return (
-        <ImageList sx={{ width: "100%", minHeight: "100%" }} cols={3} rowHeight={'auto'} >
-            {
-                itemData.map((item) => (
-                    // <ImageListItem key={item.img}>
-                    <ImageListItem key={Math.random()} >
+    return (images &&
+        <ImageList
+            sx={{
+                width: "100%",
+                minHeight: "100%"
+            }}
+            cols={3}
+            rowHeight={'auto'}
+        >
+            {images.map(item => (
 
-                        <AutoAwesomeMotionRoundedIcon sx={{
+                < ImageListItem
+                    key={item?._id}
+                >
+
+                    {/* иконка галереи на посте в ленте */}
+                    {item?.images?.length && <AutoAwesomeMotionRounded
+                        sx={{
                             color: 'white',
                             transform: 'scale(-1, 1)',
                             position: 'absolute',
                             right: '10px',
                             top: '10px',
                             fontSize: '32px'
-                        }} />
+                        }}
+                    />}
 
-                        <Box sx={{
+                    {/* пост в ленте */}
+                    <Box
+                        sx={{
                             width: '100%',
                             paddingTop: '100%',
                             backgroundSize: 'cover',
                             backgroundColor: 'black',
-                            backgroundRepeat: 'no-repeat'
+                            backgroundImage: `url(/images/noPhoto.png)`,
+                            backgroundRepeat: 'no-repeat',
+                            cursor: 'pointer',
                         }}
 
-                            style={{ backgroundImage: `url(${item.img})` }}
-
-                            // тут поставить ссылку на пост, к которому привязаны картинки
-                            onClick={() => { clicker(item.title) }}
+                        style={item?.images && (
+                            item?.images[0]?.url &&
+                            { backgroundImage: `url(${url.slice(0, -7) + item?.images[0]?.url})` }
+                        )}
 
-                        >
-
-
-                            {/* <img
-                            src={`${item.img}?w=164&h=164&fit=crop&auto=format`}
-                            srcSet={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2 2x`}
-                            alt={item.title}
-                            loading="lazy"
-                            style={{
-                                width: '100%'
-                            }}
-                        /> */}
-                        </Box>
-                    </ImageListItem>
-                ))
+                        onClick={() => {
+                            toPost(item._id)
+                        }}
+                    />
+                </ImageListItem>
+            ))
             }
         </ImageList >
-    );
+    )
 }
 
-// массив с картинками
-const itemData = [
-    {
-        img: '/images/image4.jpeg',
-        title: 'Cat',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
-        title: 'Breakfast',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',
-        title: 'Burger',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',
-        title: 'Camera',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',
-        title: 'Coffee',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',
-        title: 'Hats',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',
-        title: 'Basketball',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',
-        title: 'Fern',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',
-        title: 'Mushrooms',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',
-        title: 'Tomato basil',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',
-        title: 'Sea star',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6',
-        title: 'Bike',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
-        title: 'Breakfast',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',
-        title: 'Burger',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',
-        title: 'Camera',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',
-        title: 'Coffee',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',
-        title: 'Hats',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62',
-        title: 'Honey',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',
-        title: 'Basketball',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',
-        title: 'Fern',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',
-        title: 'Mushrooms',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',
-        title: 'Tomato basil',
-    },
-    {
-        img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',
-        title: 'Sea star',
-    }
-];
+
+export const СStandardImageList = connect(state => ({ images: state?.feed?.UserFeed?.payload }))(StandardImageList)

+ 29 - 5
js/Project/project/src/components/user/index.js

@@ -7,12 +7,13 @@ import { store } from '../redux';
 import { Box, Container, Divider } from '@mui/material';
 
 import BasicCard from './userData';
-import { actionFindUserOne } from '../redux/action';
+import { actionFindUserOne, actionFullUserFindOne } from '../redux/action';
 
-import StandardImageList from './gallery';
+import { StandardImageList, СStandardImageList } from './gallery';
 
 function User({ user = {}, loadUser }) {
 
+
     const { userId } = useParams()
     useEffect(() => { loadUser(userId) }, [userId])
 
@@ -21,7 +22,7 @@ function User({ user = {}, loadUser }) {
     const promiceStatus = store.getState()?.promise?.UserFindOne?.status
     const followingsArr = []
 
-    if (promiceStatus === 'FULFILLED' && user.following) {
+    if (promiceStatus === 'FULFILLED' && user?.following) {
         // console.log(999, user.following)
         for (let id of (user.following)) {
             for (let [key, value] of Object.entries(id)) {
@@ -42,14 +43,37 @@ function User({ user = {}, loadUser }) {
                 <Divider />
 
                 <Box >
-                    <StandardImageList />
+                    {/* <StandardImageList /> */}
+                    <СStandardImageList />
                 </Box>
             </Container>
         </React.Fragment >
     );
 }
 
-export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFindUserOne })(User)
+// export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFindUserOne })(User)
+
+export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFullUserFindOne })(User)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 // export const CUser = connect(state => ({ user: state?.promise?.UserFindOne?.payload }), { loadUser: actionFullUserFindOne })(User)
 

+ 148 - 32
js/Project/project/src/components/user/userData.js

@@ -1,68 +1,184 @@
 import * as React from 'react';
 import { useParams } from 'react-router-dom';
+import { useSelector } from 'react-redux';
 
 import { CardContent, Typography, Button, Stack, Avatar, Box, Container } from '@mui/material';
-import ManageAccountsOutlinedIcon from '@mui/icons-material/ManageAccountsOutlined';
+import { ManageAccountsOutlined, PersonRemoveRounded, PersonAddRounded } from '@mui/icons-material';
+import PersonRemoveRoundedIcon from '@mui/icons-material/PersonRemoveRounded';
 
 import { url } from "../../App"
 
-// const myId = (JSON.parse(atob(localStorage?.authToken?.split('.')[1]))).sub.id
 
+// блок пользовательских данных
+export default function BasicCard({ userData }) {
+    const urlAvatar = url.slice(0, -7) + userData?.avatar?.url
 
-let myId
-if (localStorage.authToken !== undefined) {
-    myId = (JSON.parse(atob(localStorage?.authToken?.split('.')[1]))).sub.id
-}
+    // определяем количество постов пользоваеля
+    const userPostsCount = useSelector(state => state?.promise?.UserPostsCount?.payload)
 
-// Функция отображения кнопки редактирования своего профиля на карточке юзера
-function UptadeProfile() {
+    // определяем мой id
+    const myId = useSelector(state => state?.auth?.payload?.sub?.id)
+    // определяем всех моих подписчиков
+    const myFollowingList = (useSelector(state => state?.promise?.AboutMe?.payload?.following)).map(user => user._id)
 
-    const { userId } = useParams()
+    function userFollowers() {
+        console.log('click on Followers')
+    }
 
-    if (userId === myId) {
-        return (
-            <Button variant="outlined" startIcon={<ManageAccountsOutlinedIcon />} disableRipple>Редактировать аккаунт</Button>
-        )
+    function userFollowing() {
+        console.log('click on Following')
     }
-}
 
-// блок пользовательских данных
-export default function BasicCard({ userData }) {
-    // console.log(6775, userData)
 
-    const urlAvatar = url.slice(0, -7) + userData?.avatar?.url
+    // Функция отображения кнопки редактирования своего профиля/отписки или подписки на другого юзера
+    function UptadeProfileButton() {
+
+        // вырезаем ид юзера из адресной строки
+        const { userId } = useParams()
+
+        if (userId === myId) {
+            return (
+                <Button
+                    sx={{
+                        minWidth: '300px',
+                        backgroundColor: '#ebebeb',
+                        border: 'none'
+                    }}
+                    color='inherit'
+                    variant="outlined"
+                    startIcon={
+                        <ManageAccountsOutlined
+                            color='inherit'
+                        />}
+                    disableRipple
+                    onClick={() => console.log('Нажал редактировать профиль')}
+                >
+                    Редактировать аккаунт
+                </Button>
+            )
+        }
+
+        if (myFollowingList.includes(userId)) {
+            return (
+                <Button
+                    sx={{
+                        minWidth: '300px',
+                        backgroundColor: '#ebebeb',
+                        border: 'none'
+                    }}
+                    color='inherit'
+                    variant="outlined"
+                    startIcon={
+                        <PersonRemoveRounded
+                            color='inherit'
+                        />}
+                    disableRipple
+                    onClick={() => console.log('Нажал отписаться')
+                    }
+                >
+                    Отменить подписку
+                </Button>
+            )
+        } else {
+            return (
+                <Button
+                    sx={{
+                        minWidth: '300px'
+                    }}
+                    startIcon={
+                        <PersonAddRounded />
+                    }
+                    variant="contained"
+                    disableRipple
+                    onClick={() => console.log('Нажал подписаться')
+                    }
+                >
+                    Подписаться
+                </Button>
+            )
+        }
+    }
+
 
     return (
-        <Container sx={{ display: 'flex' }}>
+        <Container sx={{
+            display: 'flex',
+            alignItems: 'center'
+        }}
+        >
             <Box>
-                <Avatar alt={userData?.login} src={urlAvatar} sx={{ width: 150, height: 150 }} />
+                <Avatar alt={userData?.login} src={urlAvatar} sx={{
+                    width: 150,
+                    height: 150
+                }}
+                />
             </Box>
 
-            <Box sx={{ display: 'flex', flexDirection: 'column', marginLeft: '50px ' }}>
-                <CardContent sx={{ flex: '1 0 auto' }}>
-                    <Stack direction="row" spacing={2}>
-                        <Typography component="div" variant="h4">
+            <Box sx={{
+                display: 'flex',
+                flexDirection: 'column',
+                marginLeft: '50px '
+            }}
+            >
+                <CardContent sx={{
+                    flex: '1 0 auto'
+                }}
+                >
+                    <Stack
+                        sx={{
+                            justifyContent: 'space-between'
+                        }}
+                        direction="row"
+                        spacing={2}
+                    >
+                        <Typography
+                            component="div"
+                            variant="h4"
+                        >
                             {userData?.login}
                         </Typography>
 
-                        <UptadeProfile />
+                        <UptadeProfileButton />
                     </Stack>
 
-                    <Typography variant="subtitle1" color="text.secondary" component="div">
-                        {userData.nick || ''}
+                    <Typography
+                        variant="subtitle1"
+                        color="text.secondary"
+                        component="div"
+                    >
+                        {userData?.nick || ''}
                     </Typography>
                 </CardContent>
 
-                <Stack direction="row" spacing={5} padding={2} paddingTop={1}>
-                    <Typography variant="subtitle1">
-                        {userData?.followers?.length || '0'} публикаций
+                <Stack
+                    direction="row"
+                    spacing={5}
+                    padding={2}
+                    paddingTop={1}
+                >
+                    <Typography
+                        variant="subtitle1"
+                    >
+                        {userPostsCount || '0'} публикаций
                     </Typography>
 
-                    <Typography variant="subtitle1">
+                    <Typography
+                        sx={{
+                            cursor: 'pointer'
+                        }}
+                        variant="subtitle1"
+                        onClick={userFollowers}
+                    >
                         {userData?.followers?.length || '0'} подписчиков
                     </Typography>
 
-                    <Typography variant="subtitle1">
+                    <Typography
+                        sx={{
+                            cursor: 'pointer'
+                        }}
+                        variant="subtitle1"
+                        onClick={userFollowing}
+                    >
                         {userData?.following?.length || '0'} подписок
                     </Typography>
                 </Stack>