Browse Source

31.03.2023 00:45

Volddemar4ik 1 year ago
parent
commit
28f9401c05
44 changed files with 1175 additions and 3526 deletions
  1. 37 127
      js/Project/project/src/App.js
  2. 55 0
      js/Project/project/src/components/404/index.js
  3. 0 8
      js/Project/project/src/components/404/page404.js
  4. 0 1
      js/Project/project/src/components/auth_reg/index.js
  5. 2 2
      js/Project/project/src/components/auth_reg/login_form.js
  6. 0 10
      js/Project/project/src/components/create_post/dnd.js
  7. 0 1
      js/Project/project/src/components/create_post/dropzone.js
  8. 5 19
      js/Project/project/src/components/create_post/index.js
  9. 0 106
      js/Project/project/src/components/create_post/test.js
  10. 39 203
      js/Project/project/src/components/feed/aboutMe.js
  11. 301 0
      js/Project/project/src/components/feed/card.js
  12. 0 146
      js/Project/project/src/components/feed/card_feed.js
  13. 63 26
      js/Project/project/src/components/feed/carousel_feed.js
  14. 14 3
      js/Project/project/src/components/feed/index.js
  15. 5 74
      js/Project/project/src/components/feed/recommended.js
  16. 2 1
      js/Project/project/src/components/feed/style.scss
  17. 0 134
      js/Project/project/src/components/feed/test.js
  18. 22 0
      js/Project/project/src/components/functions/index.js
  19. 56 45
      js/Project/project/src/components/post/carousel.js
  20. 7 359
      js/Project/project/src/components/post/comments.js
  21. 0 57
      js/Project/project/src/components/post/comments_feed_card.js
  22. 0 229
      js/Project/project/src/components/post/исходники до теста с провайдером/card_post.js
  23. 0 52
      js/Project/project/src/components/post/исходники до теста с провайдером/carousel_post.js
  24. 0 216
      js/Project/project/src/components/post/исходники до теста с провайдером/comment_card.js
  25. 0 138
      js/Project/project/src/components/post/исходники до теста с провайдером/comment_field.js
  26. 0 62
      js/Project/project/src/components/post/исходники до теста с провайдером/comments_feed.js
  27. 0 55
      js/Project/project/src/components/post/исходники до теста с провайдером/index.js
  28. 0 3
      js/Project/project/src/components/post/исходники до теста с провайдером/style.scss
  29. 1 1
      js/Project/project/src/components/structure/footer.js
  30. 0 194
      js/Project/project/src/components/structure/header(old).js
  31. 34 17
      js/Project/project/src/components/structure/header.js
  32. 112 101
      js/Project/project/src/components/structure/modal.js
  33. 5 0
      js/Project/project/src/components/structure/style.scss
  34. 9 0
      js/Project/project/src/components/update_profile/index.js
  35. 0 175
      js/Project/project/src/components/user/gallery(test).js
  36. 6 5
      js/Project/project/src/components/user/gallery.js
  37. 14 32
      js/Project/project/src/components/user/index.js
  38. 0 0
      js/Project/project/src/components/user/style.scss
  39. 130 73
      js/Project/project/src/components/user/userData.js
  40. 0 489
      js/Project/project/src/redux/action(test).js
  41. 44 233
      js/Project/project/src/redux/action.js
  42. 2 28
      js/Project/project/src/redux/index.js
  43. 23 49
      js/Project/project/src/redux/reducers.js
  44. 187 52
      js/Project/project/src/redux/thunks.js

+ 37 - 127
js/Project/project/src/App.js

@@ -1,120 +1,4 @@
-// {// старая версия
-//     // import React, { useEffect, useState } from 'react'
-//     // import { Router, Route, Redirect, withRouter, Switch } from 'react-router-dom';
-//     // import { Provider, useSelector } from 'react-redux';
-//     // import { store } from './components/redux/index';
-//     // import createHistory from "history/createBrowserHistory";
-
-//     // import './App.css';
-//     // // import logo from './logo.svg';
-
-//     // // импорт страниц
-//     // import { CUser } from './components/user';
-//     // import { CFeed } from './components/feed';
-//     // import { CComments } from './components/post';
-//     // import { CreatePost } from './components/create_post';
-//     // import AuthReg from './components/auth_reg';
-//     // // import Header from './components/structure/header';
-//     // import { CHeader } from './components/structure/header';
-//     // import Footer from './components/structure/footer';
-//     // import Search from './components/search';
-//     // import Page404 from './components/404/page404';
-
-//     // // url проекта
-//     // export const url = 'http://hipstagram.node.ed.asmer.org.ua/'
-
-
-//     // const history = createHistory()
-
-
-//     // // Приватный роутинг - удалить, если не буду использовать
-
-//     // {
-//     //     // фейковый авторизатор
-//     //     const fakeAuth = {
-//     //         isAuthenticated: false,
-//     //         authenticate(cb) {
-//     //             this.isAuthenticated = true
-//     //             setTimeout(cb, 100)
-//     //         },
-//     //         signout(cb) {
-//     //             this.isAuthenticated = false
-//     //             setTimeout(cb, 100)
-//     //         }
-//     //     }
-
-
-
-//     //     // кнопка разлогина, которая рисуется, если залогинен
-//     //     const AuthButton = withRouter(({ history }) => (
-//     //         fakeAuth.isAuthenticated && (
-//     //             <p>
-//     //                 Welcome! <button onClick={() => {
-//     //                     fakeAuth.signout(() => history.push('/'))
-//     //                 }}>Sign out</button>
-//     //             </p>
-//     //         )
-//     //     ))
-
-//     //     const PrivateRoute = ({ component: Component, ...rest }) => (
-//     //         <Route {...rest} render={(props) => (
-//     //             typeof localStorage.authToken === 'string'
-//     //                 ? <Component {...props} />
-//     //                 : <Redirect to={{
-//     //                     pathname: '/authorization',
-//     //                     state: { from: props.location }
-//     //                 }} />
-//     //         )} />
-//     //     )
-
-
-//     // }
-
-//     // // роутинг в зависимости от того.залогинен пользователь или нет
-//     // const MainRoutes = () => {
-//     //     const currentState = useSelector(state => state?.auth?.token)
-
-//     //     return (
-//     //         <main style={{ flexGrow: '1' }}>
-//     //             {currentState
-//     //                 ? <Switch>
-//     //                     <Route path="/" component={CFeed} exact />
-//     //                     <Route path="/post/:postId" component={CComments} />
-//     //                     <Route path="/user/:userId" component={CUser} />
-//     //                     <Route path="/createpost" component={CreatePost} />
-//     //                     <Route path='/search' component={Search} />
-//     //                     <Route path="*" component={Page404} />
-//     //                 </Switch>
-//     //                 : <Switch>
-//     //                     <Route path="/" component={AuthReg} exact />
-//     //                     <Route path="/registration" component={AuthReg} />
-//     //                     <Route path="*" component={Page404} />
-//     //                 </Switch>
-//     //             }
-//     //         </main>
-//     //     )
-//     // }
-
-
-//     // export default function App() {
-//     //     return (
-//     //         <Provider store={store}>
-//     //             <Router history={history}>
-//     //                 <div className="wrapper">
-//     //                     <CHeader />
-//     //                     <MainRoutes />
-//     //                     <Footer />
-//     //                 </div>
-//     //             </Router>
-//     //         </Provider>
-
-//     //     )
-//     // }
-// }
-
-
-
-import React, { createContext, useState } from 'react'
+import React, { createContext, useEffect, useState } from 'react'
 import { Router, Route, Redirect, withRouter, Switch } from 'react-router-dom';
 import { Provider, useSelector } from 'react-redux';
 import { store } from './redux/index';
@@ -130,7 +14,8 @@ import AuthReg from './components/auth_reg';
 import { CHeader } from './components/structure/header';
 import Footer from './components/structure/footer';
 import Search from './components/search';
-import Page404 from './components/404/page404';
+import Page404 from './components/404';
+import UpsertProfile from './components/update_profile';
 
 // url проекта
 export const url = 'http://hipstagram.node.ed.asmer.org.ua/'
@@ -141,13 +26,14 @@ export const ModalForCountsContext = createContext()
 export const UpdateProfile = createContext()
 
 
-const MyRouter = () => {
+const MyRoutes = () => {
     return (
         <Switch>
             <Route path="/" component={CFeed} exact />
             <Route path="/post/:postId" component={CComments} />
             <Route path="/user/:userId" component={CUser} />
             <Route path="/createpost" component={CreatePost} />
+            <Route path="/updateprofile" component={UpsertProfile} />
             <Route path='/search' component={Search} />
             <Route path="*" component={Page404} />
         </Switch>
@@ -160,26 +46,50 @@ const MainRoutes = () => {
 
     // функции управления открытием/закрытием модального окна
     const [modalName, setModalName] = useState('')
-    const [modalArray, setModalArray] = useState([])
+    const [modalArray, setModalArray] = useState(false)
     const [openModal, setOpenModal] = useState(false);
     const handleOpenModal = (param) => {
         setOpenModal(true)
+
+        // если массив = null, присваиваем ему пустой массив, иначе омодальное окно при клике не будет открываться
+        if (!param.arr) param.arr = []
+
         param.arr && setModalArray([...param.arr]) // идет проверка на null, из-за баги на беке - на комментах отдается null?
         setModalName(param.name)
     }
     const handleCloseModal = () => {
         setOpenModal(false)
-        setModalArray(modalArray.length = 0)
-        console.log('modalArray: ', modalArray)
+        setModalArray(false)
+        // console.log('modalArray: ', modalArray)
     }
 
+    // функция управления моими данными для обновления профиля
     const aboutMe = useSelector(state => state?.promise?.AboutMe?.payload)
-    const myFollowings = aboutMe?.following?.map(item => {
-        return { _id: item._id }
+
+    const [updateProfile, setUpdateProfile] = useState({
+        _id: aboutMe?._id,
+        login: aboutMe?.login,
+        nick: aboutMe?.nick,
+        avatar: { _id: aboutMe?.avatar?._id },
+        following: (aboutMe?.following?.map(item => {
+            return { _id: item._id }
+        }))
     })
 
-    // функция управления моими данными для обновления профиля
-    const [updateProfile, setUpdateProfile] = useState({ _id: aboutMe?._id, login: aboutMe?.login, nick: aboutMe?.nick, avatar: { _id: aboutMe?.avatar?._id }, following: myFollowings })
+    // обновляем данные useState при обновлении aboutMe
+    useEffect(() => {
+        setUpdateProfile({
+            _id: aboutMe?._id,
+            login: aboutMe?.login,
+            nick: aboutMe?.nick,
+            avatar: { _id: aboutMe?.avatar?._id },
+            following: (aboutMe?.following?.map(item => {
+                return { _id: item._id };
+            }))
+        })
+    }, [aboutMe])
+
+
 
 
     return (
@@ -187,7 +97,7 @@ const MainRoutes = () => {
             {currentState
                 ? <UpdateProfile.Provider value={[updateProfile, setUpdateProfile]}>
                     <ModalForCountsContext.Provider value={[modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal]}>
-                        <MyRouter />
+                        <MyRoutes />
                     </ModalForCountsContext.Provider>
                 </UpdateProfile.Provider>
                 : <Switch>

+ 55 - 0
js/Project/project/src/components/404/index.js

@@ -0,0 +1,55 @@
+import { useHistory } from 'react-router-dom'
+
+import { Typography, Box } from '@mui/material'
+
+function Page404() {
+
+    const history = useHistory()
+
+    function toMain() {
+        history.push('/')
+    }
+
+    return (
+        <Box
+            sx={{
+                marginTop: '20px',
+                padding: '16px'
+            }}
+        >
+            <Typography
+                variant="h6"
+                gutterBottom
+                color='text.primary'
+                align='center'
+            >
+                К сожалению, эта страница недоступна.
+            </Typography>
+
+            <Typography
+                variant="subtitle1"
+                gutterBottom
+                color='text.primary'
+                align='center'
+            >
+                Возможно, вы воспользовались недействительной ссылкой или страница была удалена.
+            </Typography>
+
+            <Typography
+                variant="subtitle1"
+                gutterBottom
+                color='text.primary'
+                align='center'
+                onClick={toMain}
+                sx={{
+                    cursor: 'pointer'
+                }}
+            >
+                Назад в Instagram.
+            </Typography>
+        </Box>
+    )
+}
+
+
+export default Page404

+ 0 - 8
js/Project/project/src/components/404/page404.js

@@ -1,8 +0,0 @@
-
-const Page404 = () =>
-    <div>
-        NOT FOUND
-    </div>
-
-
-export default Page404

+ 0 - 1
js/Project/project/src/components/auth_reg/index.js

@@ -20,7 +20,6 @@ function RegistrationLink() {
     let location = useLocation().pathname === '/'
 
     function regLogLink() {
-        // location ? history.push('/registration') : history.push('/')
         history.push(location ? '/registration' : '/')
     }
 

+ 2 - 2
js/Project/project/src/components/auth_reg/login_form.js

@@ -48,7 +48,7 @@ function LoginForm() {
 
     // отслеживаем введение данных в полях логин/пароль
     const [data, setData] = useState({ login: '', pass: '', confirmPass: '' })
-    console.log('data: ', data)
+    // console.log('data: ', data)
 
     //  модальное окно ошибки при реге открытие/закрытие
     const [open, setOpen] = useState(false);
@@ -323,7 +323,7 @@ function LoginForm() {
                             {
                                 location
                                     ? 'Вы ввели неправильный логин или пароль. Повторите попытку.'
-                                    : 'Введенные логин и/или пароль уже зарегистрированы'
+                                    : 'Этот логин уже зарегистрирован. Используйте другой логин или войдите в свою учетную запись.'
                             }
                         </DialogContentText>
                     </DialogContent>

+ 0 - 10
js/Project/project/src/components/create_post/dnd.js

@@ -25,17 +25,7 @@ const SortableItem = (props) => {
     const itemStyle = {
         transform: CSS.Transform.toString(transform),
         transition,
-        //width: 110,
-        //height: 30,
-        //display: "flex",
-        //alignItems: "center",
-        //paddingLeft: 5,
-        //border: "1px solid gray",
-        //borderRadius: 5,
-        //marginBottom: 5,
-        //userSelect: "none",
         cursor: "grab",
-        //boxSizing: "border-box"
     };
 
     const Render = props.render

+ 0 - 1
js/Project/project/src/components/create_post/dropzone.js

@@ -61,7 +61,6 @@ export function StyledDropzone({ onFiles }) {
             marginTop: '20px',
             marginBottom: '20px'
         }}>
-            {/* <Stack spacing={2}> */}
             <div className="container">
                 <div {...getRootProps({ style })}>
                     <input {...getInputProps()} className='getphoto' />

+ 5 - 19
js/Project/project/src/components/create_post/index.js

@@ -12,7 +12,6 @@ import Container from '@mui/material/Container';
 import SendRoundedIcon from '@mui/icons-material/SendRounded';
 
 import { StyledDropzone } from './dropzone';
-// import { actionFilesUpload, actionCreatePost, actionFullCreatePost } from '../redux/action';
 import { actionFilesUpload, actionFullCreatePost } from '../../redux/thunks';
 import { actionCreatePost } from '../../redux/action';
 import HighlightOffRoundedIcon from '@mui/icons-material/HighlightOffRounded';
@@ -22,20 +21,13 @@ import { UploadedFiles } from './uploaded_files';
 
 export const CreatePost = () => {
     const [post, setPost] = useState({ title: '', text: '', images: [] })
-    // console.log(1, "postMain: ", post)
 
     // создаем объект с данными из форм для отправки на бек
-    // const newPostData = ({ ...post, images: ((post.images).map(item => `{_id:${item._id}}`)) })
     const newPostData = ({ ...post, images: ((post.images).map(item => ({ _id: item._id }))) })
-    // console.log(33, newPostData)
-
-    // const imgForCreatePost = (post.images).map(item => item._id)
-    // console.log('images arr: ', imgForCreatePost)
 
     const dispatch = useDispatch()
 
-
-    // вот эту часть нужно будет сделать через connect
+    // вот эту часть нужно будет сделать через connect???
     const images = useSelector(state => state.promise?.FilesUpload?.payload)
 
     useEffect((() => {
@@ -58,10 +50,9 @@ export const CreatePost = () => {
     // console.log('newPost', newPost)
 
 
-
-    // какого хуя не работает await
-    const onSend = async (data) => {
-        await store.dispatch(actionFullCreatePost(data));
+    // какого хуя не работает await???
+    const onSend = async () => {
+        await store.dispatch(actionFullCreatePost(newPostData));
 
         if (newPost) {
             // console.log('щас пойдем на переход', newPost)
@@ -70,9 +61,6 @@ export const CreatePost = () => {
     }
 
 
-
-
-
     return (
         <Container maxWidth="90%">
             <Container>
@@ -111,7 +99,6 @@ export const CreatePost = () => {
                     id="text"
                     label="Описание поста"
                     multiline
-                    rows={2}
                     variant="standard"
                     onChange={e => setPost({ ...post, text: e.target.value })}
                     value={post.text}
@@ -125,9 +112,8 @@ export const CreatePost = () => {
                         <SendRoundedIcon
                             style={{ transform: 'translate(3px, -4px) rotate(-30deg)' }}
                         />}
-                    // onClick={() => { dispatch(actionCreatePost(newPostData)) }}
 
-                    onClick={() => onSend(newPostData)}
+                    onClick={onSend}
                 >
                     Опубликовать пост
                 </Button>

+ 0 - 106
js/Project/project/src/components/create_post/test.js

@@ -1,106 +0,0 @@
-import React, { useCallback, useMemo } from 'react'
-import { useDropzone } from 'react-dropzone'
-import { Box, Stack } from '@mui/system';
-
-const baseStyle = {
-    flex: 1,
-    display: 'flex',
-    flexDirection: 'column',
-    alignItems: 'center',
-    padding: '30px',
-    borderWidth: 2,
-    borderRadius: 2,
-    borderColor: '#eeeeee',
-    borderStyle: 'dashed',
-    backgroundColor: '#fafafa',
-    color: '#bdbdbd',
-    outline: 'none',
-    transition: 'border .24s ease-in-out'
-};
-
-const focusedStyle = {
-    borderColor: '#2196f3'
-};
-
-const acceptStyle = {
-    borderColor: '#00e676'
-};
-
-const rejectStyle = {
-    borderColor: '#ff1744'
-};
-
-const Item = (prop) =>
-    <Box className='imgBlock' sx={{
-        minWidth: '150px',
-        width: '15%',
-        margin: '5px',
-        textAlign: 'center'
-    }}>
-        <div>
-            тут текст
-        </div>
-    </Box>
-
-// =========================================================================
-// ========================================================================
-
-
-
-
-
-export function StyledDropzone({ onFiles }) {
-    const onDrop = useCallback(acceptedFiles => {
-        onFiles(acceptedFiles)
-    }, [])
-
-    const {
-        getRootProps,
-        getInputProps,
-        isFocused,
-        isDragAccept,
-        isDragReject
-    } = useDropzone({ onDrop, accept: { 'image/*': [] } });
-
-    const style = useMemo(() => ({
-        ...baseStyle,
-        ...(isFocused ? focusedStyle : {}),
-        ...(isDragAccept ? acceptStyle : {}),
-        ...(isDragReject ? rejectStyle : {})
-    }), [
-        isFocused,
-        isDragAccept,
-        isDragReject
-    ]);
-
-    return (
-        <Box sx={{
-            width: '100%',
-            marginTop: '20px',
-            marginBottom: '20px'
-        }}>
-            <Stack spacing={2}>
-                <div className="container">
-                    <div {...getRootProps({ style })}>
-                        <input {...getInputProps()} className='getphoto' />
-                        <p>Перетащите или добавьте, кликнув сюда, файлы</p>
-                    </div>
-                </div>
-                <Box sx={{
-                    display: 'flex',
-                    flex: 1,
-                    flexDirection: 'row',
-                    alignItems: 'flex-start',
-                    flexWrap: 'wrap',
-                    alignContent: 'flex-start',
-                    justifyContent: 'center',
-                    padding: '5px',
-                    backgroundColor: '#fafafa',
-                    color: '#bdbdbd'
-                }}>
-                    <Item />
-                </Box >
-            </Stack>
-        </Box>
-    );
-}

+ 39 - 203
js/Project/project/src/components/feed/aboutMe.js

@@ -1,190 +1,3 @@
-// {//старая версия
-//     // import React, { useState } from 'react';
-//     // import { Avatar, Typography, Box, Stack, CardHeader, Backdrop, Modal, Fade, Divider, IconButton } from '@mui/material'
-//     // import { Close } from '@mui/icons-material'
-//     // import { useSelector, connect } from 'react-redux'
-//     // import { useHistory } from 'react-router-dom'
-
-//     // import { RecommendedCard } from './recommended';
-
-//     // import './style.scss'
-
-//     // import { url } from "../../App"
-
-
-//     // const style = {
-//     //     position: 'absolute',
-//     //     top: '50%',
-//     //     left: '50%',
-//     //     transform: 'translate(-50%, -50%)',
-//     //     width: 400,
-//     //     height: 'fit-content',
-//     //     bgcolor: 'background.paper',
-//     //     boxShadow: 24,
-//     //     outline: 0,
-//     //     borderRadius: 3,
-//     //     padding: '5px 0'
-//     // };
-
-
-
-
-//     // function AboutMe({ aboutMe = {} }) {
-//     //     let history = useHistory()
-
-//     //     const myPostsCount = useSelector(state => state?.promise?.MyPostsCount?.payload)
-
-//     //     function toMyAccount() {
-//     //         history.push(`/user/${aboutMe?._id}`)
-//     //     }
-
-//     //     // функции управления открытием/закрытием модального окна
-//     //     const [modalName, setModalName] = useState('')
-//     //     const [modalArray, setModalArray] = useState([])
-//     //     const [openModal, setOpenModal] = useState(false);
-//     //     const handleOpenModal = (param) => {
-//     //         setOpenModal(true)
-//     //         setModalArray([...param.arr])
-//     //         setModalName(param.name)
-//     //     }
-
-//     //     const handleCloseModal = () => setOpenModal(false);
-
-//     //     return (
-//     //         <Box sx={{
-//     //             maxWidth: 400
-//     //         }}>
-//     //             <CardHeader
-//     //                 avatar={
-//     //                     <Avatar
-//     //                         sx={{ width: 50, height: 50 }}
-//     //                         alt={aboutMe?.login}
-//     //                         src={url + aboutMe?.avatar?.url}
-//     //                     />
-//     //                 }
-
-//     //                 subheader={
-//     //                     <Typography
-//     //                         sx={{
-//     //                             cursor: 'pointer'
-//     //                         }}
-//     //                         variant='h6'
-//     //                         onClick={toMyAccount}>
-//     //                         {aboutMe?.login}
-//     //                     </Typography>
-//     //                 }
-//     //             />
-//     //             <Stack
-//     //                 sx={{
-//     //                     paddingTop: '5px',
-//     //                     paddingLeft: '20px',
-//     //                     flexWrap: 'wrap'
-//     //                 }}
-//     //                 direction="row"
-//     //                 spacing={1}
-//     //                 padding={2}>
-//     //                 <Typography
-//     //                     sx={{
-//     //                         cursor: 'pointer'
-//     //                     }}
-//     //                     onClick={toMyAccount}
-//     //                 >
-//     //                     {myPostsCount || '0'} публикаций
-//     //                 </Typography>
-//     //                 <Typography
-//     //                     sx={{
-//     //                         cursor: 'pointer'
-//     //                     }}
-//     //                     onClick={() => handleOpenModal({ arr: aboutMe?.followers, name: 'Ваши подписчики' })}
-//     //                 >
-//     //                     {aboutMe?.followers?.length || '0'} подписчиков
-//     //                 </Typography>
-//     //                 <Typography
-//     //                     sx={{
-//     //                         cursor: 'pointer'
-//     //                     }}
-//     //                     onClick={() => handleOpenModal({ arr: aboutMe?.following, name: 'Ваши подписки' })}
-//     //                 >
-//     //                     {aboutMe?.following?.length || '0'} подписок
-//     //                 </Typography>
-//     //             </Stack>
-
-//     //             {modalArray && <Modal
-//     //                 aria-labelledby="transition-modal-title"
-//     //                 aria-describedby="transition-modal-description"
-//     //                 open={openModal}
-//     //                 onClose={handleCloseModal}
-//     //                 closeAfterTransition
-//     //                 slots={{ backdrop: Backdrop }}
-//     //                 slotProps={{
-//     //                     backdrop: {
-//     //                         timeout: 500,
-//     //                     },
-//     //                 }}
-//     //             >
-//     //                 <Fade in={openModal}>
-//     //                     <Box sx={style}>
-//     //                         <Stack
-//     //                             direction="row"
-//     //                             justifyContent="space-between"
-//     //                             alignItems="center"
-//     //                             sx={{
-//     //                                 padding: '0 24px'
-//     //                             }}>
-//     //                             <Typography
-//     //                                 variant="subtitle1"
-//     //                                 color='text.primary'
-//     //                                 gutterBottom
-//     //                             >
-//     //                                 {modalName}
-//     //                             </Typography>
-
-//     //                             <IconButton
-//     //                                 onClick={handleCloseModal}
-//     //                                 title='Закрыть'
-//     //                             >
-//     //                                 <Close
-//     //                                     sx={{
-//     //                                         position: 'absolute',
-//     //                                         color: 'black'
-//     //                                     }}
-//     //                                     fontSize='medium'
-//     //                                 />
-//     //                             </IconButton>
-//     //                         </Stack>
-
-//     //                         <Divider />
-
-//     //                         <Box
-//     //                             className='recommendedBox'
-//     //                             sx={{
-//     //                                 margin: '8px 0',
-//     //                                 width: '95%',
-//     //                                 paddingLeft: '8px',
-//     //                                 minHeight: 'fit-content',
-//     //                                 maxHeight: 350,
-//     //                                 overflowY: 'auto'
-//     //                             }}>
-//     //                             {modalArray.map(item => <RecommendedCard key={item._id} data={item} />)}
-//     //                         </Box>
-//     //                     </Box>
-//     //                 </Fade>
-//     //             </Modal>}
-//     //         </Box>
-
-//     //     )
-//     // }
-
-//     // export const CAboutMe = connect(state => ({ aboutMe: state?.promise?.AboutMe?.payload }))(AboutMe)
-// }
-
-
-
-
-
-
-
-
 import React, { useContext } from 'react';
 import { Avatar, Typography, Box, Stack, CardHeader, Backdrop, Modal, Fade, Divider, IconButton } from '@mui/material'
 import { Close } from '@mui/icons-material'
@@ -192,7 +5,7 @@ import { useSelector, connect } from 'react-redux'
 import { useHistory } from 'react-router-dom'
 
 import { RecommendedCard } from './recommended';
-import { ModalWindow } from '../structure/modal';
+import ModalWindow from '../structure/modal';
 import { ModalForCountsContext, UpdateProfile } from "../../App";
 
 import './style.scss'
@@ -228,40 +41,60 @@ function AboutMe({ aboutMe = {} }) {
     // контекст модального окна
     const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
 
+    function myFollower() {
+        handleOpenModal({ arr: aboutMe?.followers, name: 'Ваши подписчики' })
+    }
+
+    function myFollowings() {
+        handleOpenModal({ arr: aboutMe?.following, name: 'Ваши подписки' })
+    }
+
     return (
-        <Box sx={{
-            maxWidth: 400
-        }}>
+        <Box>
             <CardHeader
                 avatar={
                     <Avatar
-                        sx={{ width: 50, height: 50 }}
+                        sx={{
+                            width: 50,
+                            height: 50
+                        }}
                         alt={aboutMe?.login}
                         src={url + aboutMe?.avatar?.url}
                     />
                 }
 
-                subheader={
+                title={
                     <Typography
                         sx={{
                             cursor: 'pointer'
                         }}
                         variant='h6'
-                        onClick={toMyAccount}>
+                        onClick={toMyAccount}
+                    >
                         {aboutMe?.login}
                     </Typography>
                 }
+
+                subheader={
+                    <Typography
+                        variant='subtitle1'
+                        color='text.secondary'
+                    >
+                        {aboutMe?.nick}
+                    </Typography>
+                }
             />
             <Stack
-                sx={{
-                    paddingTop: '5px',
-                    paddingLeft: '20px',
-                    flexWrap: 'wrap'
-                }}
                 direction="row"
                 spacing={1}
-                padding={2}>
+                padding={2}
+                sx={{
+                    paddingTop: 0,
+                    // flexWrap: 'wrap'
+                }}
+            >
                 <Typography
+                    variant='body2'
                     sx={{
                         cursor: 'pointer'
                     }}
@@ -269,19 +102,23 @@ function AboutMe({ aboutMe = {} }) {
                 >
                     {myPostsCount || '0'} публикаций
                 </Typography>
+
                 <Typography
+                    variant='body2'
                     sx={{
                         cursor: 'pointer'
                     }}
-                    onClick={() => handleOpenModal({ arr: aboutMe?.followers, name: 'Ваши подписчики' })}
+                    onClick={myFollower}
                 >
                     {aboutMe?.followers?.length || '0'} подписчиков
                 </Typography>
+
                 <Typography
+                    variant='body2'
                     sx={{
                         cursor: 'pointer'
                     }}
-                    onClick={() => handleOpenModal({ arr: aboutMe?.following, name: 'Ваши подписки' })}
+                    onClick={myFollowings}
                 >
                     {aboutMe?.following?.length || '0'} подписок
                 </Typography>
@@ -289,7 +126,6 @@ function AboutMe({ aboutMe = {} }) {
 
             {modalArray && <ModalWindow />}
         </Box>
-
     )
 }
 

+ 301 - 0
js/Project/project/src/components/feed/card.js

@@ -0,0 +1,301 @@
+import { url } from '../../App';
+
+import { useState, useEffect, useContext } from 'react';
+import { useDispatch, useSelector } from "react-redux";
+import { useHistory } from 'react-router-dom';
+
+import { Card, CardHeader, CardMedia, CardContent, CardActions, Avatar, IconButton, Typography, Box, Divider, Popover, Tooltip, Checkbox } from '@mui/material'
+import PopupState, { bindTrigger, bindPopover } from 'material-ui-popup-state';
+import { FavoriteBorderRounded, FavoriteRounded, ChatBubbleOutline, TurnedInNot, SendRounded } from '@mui/icons-material/'
+import Grid from '@mui/material/Unstable_Grid2';
+
+import { actionFullAddLike, actionFullDeleteLike } from "../../redux/thunks";
+
+import { MyCarousel } from './carousel_feed';
+import { createDate } from '../functions';
+import ModalWindow from '../structure/modal';
+import { ModalForCountsContext } from '../../App';
+
+
+export function CardFeed({ postData }) {
+    // console.log('postData: ', postData)
+
+    const history = useHistory()
+    const dispatch = useDispatch()
+
+
+    // создание ссылки "поделиться"
+    const [toolTipText, setToolTipText] = useState(true)
+    let text = toolTipText ? 'Нажмите для копирования' : 'Ссылка скопирована в буфер обмена'
+
+    function copyShareLink() {
+        navigator.clipboard.writeText(window.location.href + `post/${postData?._id}`)
+
+        // меняем текст тултипа и через 2сек возрващаем на место
+        setToolTipText(!toolTipText)
+        setTimeout(() => setToolTipText(toolTipText), 2000)
+    }
+
+
+    // функция управления лайками
+    const myId = useSelector(state => state?.auth?.payload?.sub?.id)
+
+    // картинка лайка включить/отключить
+    let isLike = false
+    const [checked, setChecked] = useState(isLike)
+
+    const handleChange = (event) => {
+        setChecked(event.target.checked)
+    }
+
+    // перебираем список всех лайков и сравниваем с моим ид
+    let likeId = ''
+    postData?.likes?.some(item => {
+        if (item?.owner?._id === myId) {
+            isLike = true
+            likeId = item._id
+
+            return true
+        }
+    })
+
+    useEffect(() => setChecked(isLike), [isLike])
+
+    function likeAction() {
+        checked
+            ? dispatch(actionFullDeleteLike({ _id: likeId }))
+            : dispatch(actionFullAddLike({ post: { _id: postData?._id } }))
+    }
+
+
+    // контекст модального окна
+    const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
+
+
+    function toUser() {
+        history.push(`/user/${postData?.owner?._id}`)
+    }
+
+
+    function toPost() {
+        history.push(`/post/${postData?._id}`)
+    }
+
+
+
+    return (
+        <Card sx={{
+            width: 550
+        }}>
+            <CardHeader
+                sx={{
+                    padding: '8px'
+                }}
+
+                avatar={
+                    <Avatar
+                        sx={{
+                            width: 50,
+                            height: 50
+                        }}
+                        alt={postData?.owner?.login}
+                        src={url + postData?.owner?.avatar?.url}
+                    />
+                }
+
+                // троеточие в хедере - нужно подумать, стоит ли его добавлять и что я ним буду делать
+                // action={
+                //     <IconButton aria-label='settings'>
+                //         <MoreVert />
+                //     </IconButton>
+                // }
+
+                title={
+                    <Typography
+                        variant='subtitle1'
+                        sx={{
+                            cursor: 'pointer'
+                        }}
+                        onClick={toUser}
+                    >
+                        {postData?.owner?.login}
+                    </Typography>
+                }
+
+                subheader={
+                    createDate(+postData?.createdAt)
+                }
+            />
+
+            <CardMedia>
+                <MyCarousel images={postData?.images} />
+            </CardMedia>
+
+            <CardActions
+                disableSpacing
+                sx={{
+                    padding: '0 8px'
+                }}
+            >
+                <Box sx={{
+                    flexGrow: 1,
+                }}>
+                    <Grid container spacing={2}>
+                        <Grid
+                            xs={12}
+                            container
+                            justifyContent='space-between'
+                            alignItems='center'
+                            flexDirection={{
+                                xs: 'column',
+                                sm: 'row'
+                            }}
+                            sx={{
+                                fontSize: '12px'
+                            }}
+                        >
+                            <Grid
+                                container
+                                columnSpacing={1}
+                            >
+                                {/* Лайк */}
+                                <Grid>
+                                    <Checkbox
+                                        checked={checked}
+                                        onChange={handleChange}
+                                        inputProps={{
+                                            'aria-label': 'controlled'
+                                        }}
+                                        icon={<FavoriteBorderRounded />}
+                                        checkedIcon={<FavoriteRounded />}
+                                        onClick={likeAction}
+                                        sx={{
+                                            '&.Mui-checked': {
+                                                color: 'red',
+                                            },
+                                        }}
+                                    />
+                                </Grid>
+
+                                {/* Коммент */}
+                                <Grid>
+                                    <IconButton
+                                        onClick={toPost}
+                                    >
+                                        <ChatBubbleOutline />
+                                    </IconButton>
+                                </Grid>
+
+                                {/* Расшаривание */}
+                                <Grid>
+                                    <PopupState variant="popover" popupId="demo-popup-popover">
+                                        {(popupState) => (
+                                            <div>
+                                                <IconButton
+                                                    aria-label="share"
+                                                    {...bindTrigger(popupState)}
+                                                >
+                                                    <SendRounded
+                                                        sx={{ transform: 'translate(3px, -4px) rotate(-30deg)' }}
+                                                    />
+                                                </IconButton>
+                                                <Popover
+                                                    {...bindPopover(popupState)}
+                                                    anchorOrigin={{
+                                                        vertical: 'center',
+                                                        horizontal: 'right',
+                                                    }}
+                                                    transformOrigin={{
+                                                        vertical: 'center',
+                                                        horizontal: 'left',
+                                                    }}
+                                                >
+                                                    <Tooltip title={text} placement='bottom-start'>
+                                                        <Typography
+                                                            sx={{ p: 2 }}
+                                                            onClick={copyShareLink}
+                                                        >
+                                                            {window.location.href + `post/${postData?._id}`}
+                                                        </Typography>
+                                                    </Tooltip>
+                                                </Popover>
+                                            </div>
+                                        )}
+                                    </PopupState>
+                                </Grid>
+                            </Grid>
+
+
+                            {/* Добавление в изобранное */}
+                            <Grid>
+                                <IconButton>
+                                    <TurnedInNot />
+                                </IconButton>
+                            </Grid>
+                        </Grid>
+                    </Grid>
+                </Box>
+            </CardActions>
+
+            <CardContent
+                sx={{
+                    padding: '8px',
+                    paddingTop: '0'
+                }}
+            >
+                <Typography
+                    variant='subtitle2'
+                    onClick={() => handleOpenModal({ arr: postData?.likes, name: 'Отметки "Нравится"' })}
+                    sx={{
+                        cursor: 'pointer',
+                        width: 'fit-content'
+                    }}
+                >
+                    {postData?.likes?.length || '0'} отметок "Нравится"
+                </Typography>
+            </CardContent>
+
+            <CardContent
+                sx={{
+                    padding: '8px',
+                    paddingTop: '0'
+                }}
+            >
+                <Typography
+                    variant='body1'
+                    color='text.primary'
+                >
+                    {postData?.title}
+                </Typography>
+
+                <Typography
+                    variant='body2'
+                    color='text.secondary'
+                >
+                    {postData?.text}
+                </Typography>
+            </CardContent>
+
+            <CardActions
+                sx={{
+                    padding: '8px',
+                    paddingTop: '0'
+                }}
+            >
+                <Typography
+                    variant='subtitle2'
+                    color='text.secondary'
+                    sx={{
+                        cursor: 'pointer'
+                    }}
+                    onClick={toPost}
+                >
+                    Посмотреть все комментарии ({postData?.comments?.length || '0'})
+                </Typography>
+
+            </CardActions>
+
+            <Divider />
+        </Card >
+    )
+}

+ 0 - 146
js/Project/project/src/components/feed/card_feed.js

@@ -1,146 +0,0 @@
-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 { useHistory } from 'react-router-dom';
-
-import { MyCarousel } from './carousel_feed';
-
-import { url } from '../../App';
-
-export function CardFeed({ postData }) {
-    // console.log('postData: ', 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
-        }}>
-            <CardHeader
-                avatar={
-                    <Avatar
-                        sx={{ width: 50, height: 50 }}
-                        alt={postData?.owner?.login}
-                        src={url + postData?.owner?.avatar?.url}
-                    />
-                }
-
-                // троеточие в хедере - нужно подумать, стоит ли его добавлять и что я ним буду делать
-                // action={
-                //     <IconButton aria-label="settings">
-                //         <MoreVert />
-                //     </IconButton>
-                // }
-
-                title={
-                    <Typography
-                        sx={{
-                            cursor: 'pointer'
-                        }}
-                        onClick={toUser}
-                    >
-                        {postData?.owner?.login}
-                    </Typography>
-                }
-
-                subheader={dateOfPostParse}
-            />
-
-            <CardMedia>
-                <MyCarousel images={postData?.images} />
-            </CardMedia>
-
-            <CardActions disableSpacing>
-                <Box sx={{ flexGrow: 1 }}>
-                    <Grid container spacing={2}>
-                        <Grid
-                            xs={12}
-                            container
-                            justifyContent="space-between"
-                            alignItems="center"
-                            flexDirection={{ xs: 'column', sm: 'row' }}
-                            sx={{ fontSize: '12px' }}
-                        >
-                            <Grid
-                                container
-                                columnSpacing={1}
-                                sx={{
-                                    order: { xs: 2, sm: 1 }
-                                }}
-                            >
-                                {/* Это лайк */}
-                                <Grid>
-                                    <IconButton aria-label="add to favorites">
-                                        <FavoriteBorderRounded />
-                                    </IconButton>
-                                </Grid>
-
-                                {/* Это отправка сообщения - нужно или нет? */}
-                                <Grid>
-                                    <IconButton>
-                                        <ChatBubbleOutline />
-                                    </IconButton>
-                                </Grid>
-
-                                {/* Это кнопка пошарить - может сделать ссылку на пост и при нажатии копировать ее в буфер */}
-                                <Grid>
-                                    <IconButton aria-label="share">
-                                        <Send />
-                                    </IconButton>
-                                </Grid>
-                            </Grid>
-
-                            <Grid
-                                sx={{
-                                    order: { xs: 1, sm: 2 }
-                                }}>
-                                {/* Это иконка добавления в избранное - нужно ли? */}
-                                <IconButton>
-                                    <TurnedInNot />
-                                </IconButton>
-                            </Grid>
-                        </Grid>
-                    </Grid>
-                </Box>
-            </CardActions>
-
-            <CardActions>
-                Нравится: {postData?.likes?.length || '0'}
-            </CardActions>
-
-            <CardContent>
-                <Typography variant="subtitle2" color="text.secondary">
-                    {postData?.title}
-                </Typography>
-                <Typography variant="body2" color="text.secondary">
-                    {postData?.text}
-                </Typography>
-            </CardContent>
-
-            <CardActions>
-                <Typography
-                    sx={{
-                        cursor: 'pointer'
-                    }}
-                    onClick={toPost}>
-                    Посмотреть все комментарии ({postData?.comments?.length || '0'})
-                </Typography>
-
-            </CardActions>
-
-            <Divider />
-        </Card >
-    )
-}

+ 63 - 26
js/Project/project/src/components/feed/carousel_feed.js

@@ -1,50 +1,87 @@
-import Carousel from 'react-material-ui-carousel'
+import { url } from '../../App'
 
+import Carousel from 'react-material-ui-carousel'
 import { Box } from '@mui/material'
 
-import { url } from '../../App'
+import './style.scss'
+
+
+function DynamicItem({ item }) {
 
-function Item(imgUrl) {
     return (
         <Box
-            style={{
+            className={`slider-container-${item?._id}`}
+            sx={{
                 width: '100%',
-                height: 625,
-                backgroundColor: 'black',
+                height: '625px',
                 display: 'flex',
-                alignItems: 'center'
+                justifyContent: 'center',
+                alignItems: 'center',
+                backgroundColor: '#000'
             }}>
 
             <img
                 alt='image'
-                src={imgUrl?.item && (url + imgUrl?.item)}
+                src={item?.url && (url + item?.url)}
                 style={{
-                    width: '100%'
+                    objectFit: 'contain',
+                    height: '100%',
                 }}
             />
-        </Box>
+        </Box >
     )
 }
 
-export function MyCarousel({ images }) {
 
+// контейнер для одной картинки
+function StaticItem({ item }) {
+    return (
+        <Box
+            className={`slider-container-${item?._id}`}
+            sx={{
+                display: 'flex',
+                justifyContent: 'center',
+                alignItems: 'center',
+                backgroundColor: '#000'
+            }}>
+
+            <img
+                alt='image'
+                src={item?.url && (url + item?.url)}
+                style={{
+                    objectFit: 'contain',
+                    height: '100%',
+                    width: '100%',
+                    maxHeight: '625px',
+                    maxWidth: '550px',
+                }}
+            />
+        </Box >
+    )
+}
+
+
+export function MyCarousel({ images }) {
     return (
-        <Carousel
-            autoPlay={false}
-            cycleNavigation={false}
-            animation={'slide'}
-            indicatorContainerProps={{
-                style: {
-                    marginTop: '-50px',
-                    zIndex: 999,
-                    position: 'inherit'
-                }
-            }}
-        >
-            {
-                images && images.map((item, i) => <Item key={i} item={item?.url} />)
+        <Box>
+            {images?.length > 1
+                ? <Carousel
+                    autoPlay={false}
+                    cycleNavigation={false}
+                    animation={'slide'}
+                    indicatorContainerProps={{
+                        style: {
+                            marginTop: '-50px',
+                            zIndex: 999,
+                            position: 'inherit'
+                        }
+                    }}
+                >
+                    {images && images.map((item) => <DynamicItem key={item?._id} item={item} />)}
+                </Carousel >
+                : images && images.map((item) => <StaticItem key={item?._id} item={item} />)
             }
-        </Carousel >
+        </Box>
     )
 }
 

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

@@ -8,7 +8,7 @@ import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
 
 // import { actionAboutMe, actionDownloadFeed } from '../redux/action';
 import { actionAboutMe, actionDownloadFeed } from '../../redux/thunks';
-import { CardFeed } from './card_feed';
+import { CardFeed } from './card';
 import { CAboutMe } from './aboutMe';
 import Recommended from './recommended';
 
@@ -60,12 +60,23 @@ function Feed({ feed, loadFeed }) {
                 flexGrow: 1
             }}>
                 <Grid2 container spacing={2}>
-                    <Grid2 xs={7}>
+                    <Grid2
+                        xs={7}
+                        sx={{
+                            display: 'flex',
+                            justifyContent: 'center'
+                        }}
+                    >
                         <Item>
                             {feed && feed.map(post => <Item key={post._id}><CardFeed postData={post} /></Item>)}
                         </Item>
                     </Grid2>
-                    <Grid2 xs={5}>
+                    <Grid2 xs={5}
+                        sx={{
+                            display: 'flex',
+                            justifyContent: 'center'
+                        }}
+                    >
                         <Item sx={{
                             position: 'fixed'
                         }}>

+ 5 - 74
js/Project/project/src/components/feed/recommended.js

@@ -5,77 +5,9 @@ import { useDispatch, useSelector } from "react-redux"
 import { useHistory } from 'react-router-dom';
 
 import { CardHeader, Avatar, Typography, Button, Box, Divider } from '@mui/material';
-import { UpdateProfile } from '../../App';
-import { actionFullUpdateProfile } from '../../redux/thunks';
 
 import './style.scss'
-
-
-// Карточки блока с рекомендациями
-// export function RecommendedCard({ data }) {
-function RecommendedCard({ data }) {
-    // console.log('data: ', data)
-    const history = useHistory()
-    const dispatch = useDispatch()
-
-    // контекст обновления профиля
-    const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
-    // console.log('test context3: ', updateProfile)
-
-    // проверка, является ли пользователь моим подписчиком (нужна, потому что эта карта дергается для модалки)
-    const myFollowings = useSelector(state => state?.promise?.AboutMe?.payload?.following)
-    const isFollowing = myFollowings && myFollowings.some(item => item._id === data._id)
-
-    // переход на аккаунт
-    function toAccount() {
-        history.push(`user/${data._id}`)
-    }
-
-    // функция подписки/отписки
-    function subscribing() {
-        const newData = ({
-            ...updateProfile, following: [...updateProfile.following, updateProfile.following.push({ _id: data._id })]
-        })
-
-        newData && dispatch(actionFullUpdateProfile(updateProfile))
-    }
-
-    return (
-        <CardHeader
-            avatar={
-                <Avatar
-                    src={url + data?.avatar?.url}
-                    aria-label="recipe"
-                    sx={{ width: 36, height: 36 }}
-                />
-            }
-
-            action={
-                <Button
-                    variant="text"
-                    size="small"
-                    onClick={subscribing}
-                >
-                    Подписаться
-                </Button>
-            }
-
-            title={
-                <Typography
-                    sx={{
-                        cursor: 'pointer',
-                        width: 'fit-content'
-                    }}
-                    variant="subtitle2"
-                    color='text.secondary'
-                    onClick={toAccount}
-                >
-                    {data?.login || 'Anonim'}
-                </Typography>
-            }
-        />
-    )
-}
+import { RecommendedCard } from '../structure/modal';
 
 
 // Блок с рекомендациями
@@ -99,10 +31,10 @@ function Recommended() {
         )
 
         // теперь оставляем в массиве только random рандомных элементов элементов
-        const random = 10
+        const random = 20
         for (let i = 0; i < random; i++) {
             const randomIndex = Math.floor(Math.random() * joinArr.length)
-            recommendedList.push(joinArr[randomIndex])
+            recommendedList?.push(joinArr[randomIndex])
 
             // удаляем элементы из массива, чтобы снова их не показывать
             joinArr.splice(randomIndex, 1)
@@ -131,10 +63,9 @@ function Recommended() {
             <Box
                 className='recommendedBox'
                 sx={{
-                    margin: '8px 0',
+                    margin: '8px',
                     width: '95%',
-                    paddingLeft: '8px',
-                    height: '300px',
+                    height: '400px',
                     overflowY: 'auto'
                 }}>
                 {recommendedList && recommendedList.map(item => <RecommendedCard key={item._id} data={item} />)}

+ 2 - 1
js/Project/project/src/components/feed/style.scss

@@ -24,5 +24,6 @@
 
 .recommendedBox::-webkit-scrollbar-thumb {
     border-radius: 8px;
-    background: linear-gradient(180deg, #00c6fb, #005bea);
+    // background: linear-gradient(180deg, #00c6fb, #005bea);
+    background-color: #e8def8;
 }

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

@@ -1,134 +0,0 @@
-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 { actionAboutMe, actionDownloadFeed } from '../redux/thunks';
-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)

+ 22 - 0
js/Project/project/src/components/functions/index.js

@@ -0,0 +1,22 @@
+// Функция формирования даты поста
+
+export function createDate(date) {
+    const dateOfPost = new Date(date)
+
+    const months = [
+        'января',
+        'февраля',
+        'марта',
+        'апреля',
+        'мая',
+        'июня',
+        'июля',
+        'августа',
+        'сентября',
+        'октября',
+        'ноября',
+        'декабря'
+    ]
+
+    return `${dateOfPost.getDate() < 10 ? '0' + dateOfPost.getDate() : dateOfPost.getDate()} ${months[dateOfPost.getMonth()]} ${dateOfPost.getFullYear()}  ${dateOfPost.getHours()}:${dateOfPost.getMinutes() < 10 ? '0' + dateOfPost.getMinutes() : dateOfPost.getMinutes()}`
+}

+ 56 - 45
js/Project/project/src/components/post/carousel.js

@@ -8,53 +8,65 @@ import { url } from '../../App';
 export function MyCarouselPost({ postImages }) {
     // console.log('postImages: ', postImages)
 
-    return (postImages &&
-        <Carousel
-            autoPlay={false}
-            cycleNavigation={false}
-            animation={"slide"}
-            indicatorContainerProps={{
-                style: {
-                    marginTop: '-50px',
-                    zIndex: 999,
-                    position: 'inherit'
-                }
-            }}
-            className='myCarousel'
+    return (
+        <>
+            {postImages?.length > 1
+                ? <Carousel
+                    autoPlay={false}
+                    cycleNavigation={false}
+                    animation={"slide"}
+                    indicatorContainerProps={{
+                        style: {
+                            marginTop: '-50px',
+                            zIndex: 999,
+                            position: 'inherit'
+                        }
+                    }}
+                    className='myCarousel'
 
-            sx={{
-                // flexDirection: 'column',
-                // flex: '1 1 auto',
-                width: '100%',
-                height: '100%',
+                    sx={{
+                        width: '100%',
+                        height: '100%',
 
-            }}
+                    }}
 
-        >
-            {
-                postImages.map(item =>
-                    // <Box
-                    //     sx={{
-                    //         paddingTop: '100%',
-                    //         backgroundSize: 'contain',
-                    //         backgroundColor: 'black',
-                    //         backgroundImage: `url(/images/noPhoto.png)`,
-                    //         backgroundRepeat: 'no-repeat',
-                    //         backgroundPosition: 'center',
-                    //     }}
+                >
+                    {postImages && postImages?.map(item =>
+                        <Box
+                            sx={{
+                                width: '100%',
+                                height: '743px',
+                                overflow: 'hidden',
+                                position: 'relative'
+                            }}
 
-                    //     key={item?.url}
+                            key={item?.url}
+                        >
+                            <img
+                                style={{
+                                    position: 'absolute',
+                                    top: '50%',
+                                    left: '50%',
+                                    transform: 'translate(-50%, -50%)',
+                                    width: '100%',
+                                    height: '100%',
+                                    objectFit: 'contain',
+                                    objectPosition: 'center'
+                                }}
 
-                    //     style={item?.url &&
-                    //         { backgroundImage: `url(${url + item.url})` }
-                    //     }
-                    // />
-                    <Box sx={{
-                        width: '100%',
-                        height: '743px',
-                        overflow: 'hidden',
-                        position: 'relative'
-                    }}
+                                src={url + item.url}
+                            />
+                        </Box>
+                    )}
+                </Carousel >
+                : postImages && postImages?.map(item =>
+                    <Box
+                        sx={{
+                            width: '100%',
+                            height: '743px',
+                            overflow: 'hidden',
+                            position: 'relative'
+                        }}
 
                         key={item?.url}
                     >
@@ -73,9 +85,8 @@ export function MyCarouselPost({ postImages }) {
                             src={url + item.url}
                         />
                     </Box>
-                )
-            }
-        </Carousel >
+                )}
+        </>
     )
 }
 

+ 7 - 359
js/Project/project/src/components/post/comments.js

@@ -1,334 +1,3 @@
-// {//старые настройки
-//     // import { url } from "../../App";
-//     // import { useHistory, useParams } from "react-router-dom";
-//     // import { createContext, useState, useContext } from "react";
-
-//     // import './style.scss'
-
-//     // import {
-//     //     Card,
-//     //     CardHeader,
-//     //     CardContent,
-//     //     CardActions,
-//     //     Avatar,
-//     //     IconButton,
-//     //     Typography,
-//     //     Box,
-//     //     Divider,
-//     //     Popover,
-//     //     Tooltip
-//     // } from '@mui/material'
-
-//     // import {
-//     //     FavoriteBorderRounded,
-//     //     SendRounded,
-//     //     ChatBubbleOutline,
-//     //     TurnedInNot,
-//     // } from '@mui/icons-material/'
-
-//     // import PopupState, { bindTrigger, bindPopover } from 'material-ui-popup-state';
-
-//     // import { actionFullAddLike, actionFullDeleteLike } from "../redux/thunks";
-
-//     // import Grid from '@mui/material/Unstable_Grid2';
-
-//     // import { ModalForCountsContext } from "../../App";
-
-//     // import CCommentsFeed from "./comments_feed";
-//     // import CommentField from "./comments_add_field";
-//     // import { ModalWindow } from "../structure/modal";
-//     // import { useDispatch } from "react-redux";
-
-//     // // контекст для управления состоянием данных для отправки комментов
-//     // export const CommentContext = createContext()
-
-
-//     // export function CardPost({ postData }) {
-//     //     // console.log('postData: ', postData)
-
-//     //     const dispatch = useDispatch()
-
-//     //     const history = useHistory()
-//     //     const { postId } = useParams()
-
-//     //     // отслеживаем состояние поля ввода комментария для поста
-//     //     const [comment, setComment] = useState({ text: '', post: { _id: postId } })
-
-//     //     // отслеживаем, к какой именно сущности будет добавляться лайк
-//     //     const [like, setLike] = useState({})
-
-//     //     // контекст модального окна
-//     //     const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
-
-//     //     // дата поста
-//     //     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 toAccount() {
-//     //         history.push(`/user/${postData?.owner?._id}`)
-//     //     }
-
-//     //     // перевод фокуса на строку добавления комментария при клике на иконку комментария
-//     //     function addCommentFocus() {
-//     //         document.getElementById('addCommentField').focus()
-//     //     }
-
-//     //     // отслеживание состояния текста для ToolTips
-//     //     const [toolTipText, setToolTipText] = useState(true)
-//     //     let text = toolTipText ? 'Нажмите для копирования' : 'Ссылка скопирована в буфер обмена'
-
-//     //     // функция копирования ссылки на пост
-//     //     function copyShareLink() {
-//     //         // получаем урл текущей страницы
-//     //         const postUrl = window.location.href
-//     //         navigator.clipboard.writeText(postUrl)
-
-//     //         setToolTipText(!toolTipText)
-//     //         setTimeout(() => setToolTipText(toolTipText), 2000)
-//     //     }
-
-//     //     // функция отображения всех, кому понравился пост
-//     //     // function showLikesOwner() {
-//     //     //     console.log('покажи нас')
-//     //     // }
-
-
-//     //     // функия добавления лайков
-//     //     function addLike(params) {
-//     //         dispatch(actionFullAddLike(params))
-//     //     }
-
-
-
-
-//     //     return (
-//     //         <CommentContext.Provider value={[comment, setComment]}>
-//     //             <Card
-//     //                 sx={{
-//     //                     display: 'flex',
-//     //                     flexDirection: 'column',
-//     //                     minHeight: '80vh',
-//     //                     borderRadius: 0,
-//     //                     height: '100%',
-//     //                     maxHeight: 450,
-//     //                 }}
-//     //             >
-//     //                 <CardHeader
-//     //                     avatar={
-//     //                         <Avatar
-//     //                             alt={postData?.owner?.login}
-//     //                             src={(url + postData?.owner?.avatar?.url)}
-//     //                             sx={{
-//     //                                 width: 50,
-//     //                                 height: 50
-//     //                             }}
-//     //                         />
-//     //                     }
-
-//     //                     title={
-//     //                         <Typography
-//     //                             sx={{
-//     //                                 cursor: 'pointer'
-//     //                             }}
-//     //                             onClick={toAccount}
-//     //                         >
-//     //                             {postData?.owner?.login}
-//     //                         </Typography>
-//     //                     }
-
-//     //                     subheader={dateofPostParse}
-//     //                 />
-
-//     //                 <Divider />
-
-//     //                 <CardContent
-//     //                     className='post-comments'
-
-//     //                     sx={{
-//     //                         // flex: '0 0 450px',
-//     //                         flex: '1 1 auto',
-//     //                         overflowY: 'auto',
-//     //                     }}
-//     //                 >
-//     //                     <Grid
-//     //                         container
-//     //                         spacing={2}
-//     //                         sx={{
-//     //                             marginBottom: 3
-//     //                         }}
-//     //                     >
-//     //                         <Grid
-//     //                             xs={2}
-//     //                             sx={{
-//     //                                 flex: '0 0 45px'
-//     //                             }}
-//     //                         >
-//     //                             <Avatar
-//     //                                 alt={postData?.owner?.login}
-//     //                                 src={(url + postData?.owner?.avatar?.url)}
-//     //                                 sx={{
-//     //                                     width: 40,
-//     //                                     height: 40
-//     //                                 }}
-//     //                             />
-//     //                         </Grid>
-
-//     //                         <Grid
-//     //                             xs={10}
-//     //                         >
-//     //                             <Typography
-//     //                                 variant="subtitle2"
-//     //                                 color="text.secondary"
-//     //                                 sx={{
-//     //                                     cursor: 'pointer',
-//     //                                     width: 'fit-content'
-//     //                                 }}
-//     //                                 onClick={toAccount}
-//     //                             >
-//     //                                 {postData?.owner?.login}
-//     //                             </Typography>
-
-//     //                             <Typography
-//     //                                 variant="subtitle2"
-//     //                                 color="text.primary"
-//     //                             >
-//     //                                 {postData?.title}
-//     //                             </Typography>
-
-//     //                             <Typography
-//     //                                 variant="body2"
-//     //                                 color="text.secondary"
-//     //                             >
-//     //                                 {postData?.text}
-//     //                             </Typography>
-//     //                         </Grid>
-//     //                     </Grid>
-
-//     //                     <Grid>
-//     //                         <CCommentsFeed />
-//     //                     </Grid>
-//     //                 </CardContent>
-
-//     //                 <Divider />
-
-//     //                 <Box>
-//     //                     <CardActions disableSpacing>
-//     //                         <Box
-//     //                             sx={{
-//     //                                 flexGrow: 1
-//     //                             }}>
-//     //                             <Grid
-//     //                                 xs={12}
-//     //                                 container
-//     //                                 justifyContent="space-between"
-//     //                                 alignItems="center"
-//     //                                 sx={{
-//     //                                     fontSize: '12px'
-//     //                                 }}
-//     //                             >
-//     //                                 <Grid container>
-//     //                                     <Grid>
-//     //                                         <IconButton
-//     //                                             aria-label="add to favorites"
-//     //                                             onClick={() => addLike({ post: { _id: postData?._id } })}
-//     //                                         >
-//     //                                             <FavoriteBorderRounded />
-//     //                                         </IconButton>
-//     //                                     </Grid>
-
-//     //                                     <Grid>
-//     //                                         <IconButton
-//     //                                             onClick={addCommentFocus}
-//     //                                         >
-//     //                                             <ChatBubbleOutline />
-//     //                                         </IconButton>
-//     //                                     </Grid>
-
-//     //                                     <Grid>
-//     //                                         <PopupState variant="popover" popupId="demo-popup-popover">
-//     //                                             {(popupState) => (
-//     //                                                 <div>
-//     //                                                     <IconButton
-//     //                                                         aria-label="share"
-//     //                                                         {...bindTrigger(popupState)}
-//     //                                                     >
-//     //                                                         <SendRounded
-//     //                                                             style={{ transform: 'translate(3px, -4px) rotate(-30deg)' }}
-//     //                                                         />
-//     //                                                     </IconButton>
-//     //                                                     <Popover
-//     //                                                         {...bindPopover(popupState)}
-//     //                                                         anchorOrigin={{
-//     //                                                             vertical: 'center',
-//     //                                                             horizontal: 'right',
-//     //                                                         }}
-//     //                                                         transformOrigin={{
-//     //                                                             vertical: 'center',
-//     //                                                             horizontal: 'left',
-//     //                                                         }}
-//     //                                                     >
-//     //                                                         <Tooltip title={text} placement='bottom-start'>
-//     //                                                             <Typography
-//     //                                                                 sx={{ p: 2 }}
-//     //                                                                 onClick={copyShareLink}
-//     //                                                             >
-//     //                                                                 {window.location.href}
-//     //                                                             </Typography>
-//     //                                                         </Tooltip>
-//     //                                                     </Popover>
-//     //                                                 </div>
-//     //                                             )}
-//     //                                         </PopupState>
-//     //                                     </Grid>
-//     //                                 </Grid>
-
-//     //                                 <Grid>
-//     //                                     <IconButton>
-//     //                                         <TurnedInNot />
-//     //                                     </IconButton>
-//     //                                 </Grid>
-//     //                             </Grid>
-
-//     //                             <Grid container>
-//     //                                 <Typography
-//     //                                     variant="subtitle1"
-//     //                                     color="text.secondary"
-//     //                                     sx={{
-//     //                                         padding: 1,
-//     //                                         cursor: 'pointer'
-//     //                                     }}
-//     //                                     // onClick={showLikesOwner}
-//     //                                     onClick={() => handleOpenModal({ arr: postData?.likes, name: 'Отметки "Нравится"' })}
-//     //                                 >
-//     //                                     Нравится: {postData?.likes?.length || '0'}
-//     //                                 </Typography>
-//     //                             </Grid>
-//     //                         </Box>
-
-//     //                         {modalArray && <ModalWindow />}
-//     //                     </CardActions>
-
-//     //                     <Divider />
-
-//     //                     <CardActions>
-//     //                         <CommentField />
-//     //                     </CardActions>
-//     //                 </Box>
-//     //             </Card>
-//     //         </CommentContext.Provider >
-//     //     )
-//     // }
-// }
-
-
-
-
-
 import { url } from "../../App";
 import { useHistory, useParams } from "react-router-dom";
 import { createContext, useState, useContext, useEffect } from "react";
@@ -366,9 +35,11 @@ import Grid from '@mui/material/Unstable_Grid2';
 
 import { ModalForCountsContext, UpdateProfile } from "../../App";
 
+import { createDate } from "../functions";
+
 import CCommentsFeed from "./comments_feed";
 import CommentField from "./comments_add_field";
-import { ModalWindow } from "../structure/modal";
+import ModalWindow from "../structure/modal";
 import { useDispatch, useSelector } from "react-redux";
 
 // контекст для управления состоянием данных для отправки комментов
@@ -388,18 +59,11 @@ export function CardPost({ postData }) {
 
     // контекст модального окна
     const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
+    // console.log('modalArray: ', modalArray)
 
     // контекст обновления профиля
     const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
 
-    // дата поста
-    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 toAccount() {
         history.push(`/user/${postData?.owner?._id}`)
@@ -501,7 +165,7 @@ export function CardPost({ postData }) {
                         </Typography>
                     }
 
-                    subheader={dateofPostParse}
+                    subheader={createDate(+postData?.createdAt)}
                 />
 
                 <Divider />
@@ -510,7 +174,6 @@ export function CardPost({ postData }) {
                     className='post-comments'
 
                     sx={{
-                        // flex: '0 0 450px',
                         flex: '1 1 auto',
                         overflowY: 'auto',
                     }}
@@ -577,7 +240,7 @@ export function CardPost({ postData }) {
                 <Divider />
 
                 <Box>
-                    <CardActions disableSpacing>
+                    <CardActions disableSpacing className='qrrr'>
                         <Box
                             sx={{
                                 flexGrow: 1
@@ -593,14 +256,6 @@ export function CardPost({ postData }) {
                             >
                                 <Grid container>
                                     <Grid>
-                                        {/* <IconButton
-                                            aria-label="add to favorites"
-                                            onClick={() => addLike({ post: { _id: postData?._id } })}
-                                        > */}
-                                        {/* <FavoriteBorderRounded /> */}
-
-
-
                                         <Checkbox
                                             checked={checked}
                                             onChange={handleChange}
@@ -615,12 +270,6 @@ export function CardPost({ postData }) {
                                                 },
                                             }}
                                         />
-
-
-
-
-
-                                        {/* </IconButton> */}
                                     </Grid>
 
                                     <Grid>
@@ -640,7 +289,7 @@ export function CardPost({ postData }) {
                                                         {...bindTrigger(popupState)}
                                                     >
                                                         <SendRounded
-                                                            style={{ transform: 'translate(3px, -4px) rotate(-30deg)' }}
+                                                            sx={{ transform: 'translate(3px, -4px) rotate(-30deg)' }}
                                                         />
                                                     </IconButton>
                                                     <Popover
@@ -685,7 +334,6 @@ export function CardPost({ postData }) {
                                         cursor: 'pointer'
                                     }}
                                     onClick={showLikesOwner}
-                                // onClick={() => handleOpenModal({ arr: postData?.likes, name: 'Отметки "Нравится"' })}
                                 >
                                     Нравится: {postData?.likes?.length || '0'}
                                 </Typography>

+ 0 - 57
js/Project/project/src/components/post/comments_feed_card.js

@@ -61,8 +61,6 @@ function CommentCard({ data }) {
     }
 
 
-
-
     // ============= функция отображения лайка (старт) =========================
     // чекаем мой ид
     const myId = useSelector(state => state?.auth?.payload?.sub?.id)
@@ -97,10 +95,6 @@ function CommentCard({ data }) {
     // ============= функция отображения лайка (финиш) =========================
 
 
-
-
-
-
     // функция перехода на страницу профиля
     function toAccount(id) {
         history.push(`/user/${id}`)
@@ -177,34 +171,6 @@ function CommentCard({ data }) {
                         paddingBottom: 0
                     }}
                 >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-                    {/* <IconButton
-                        onClick={() => (console.log('click like comment'))}
-                    >
-                        <FavoriteBorderRounded
-                            fontSize='small'
-                        />
-                    </IconButton> */}
-
-
-
-
-
-
                     <Checkbox
                         checked={checked}
                         onChange={handleChange}
@@ -219,29 +185,6 @@ function CommentCard({ data }) {
                             },
                         }}
                     />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
                 </Grid>
 
                 <Grid

+ 0 - 229
js/Project/project/src/components/post/исходники до теста с провайдером/card_post.js

@@ -1,229 +0,0 @@
-import { url } from "../../App";
-import { useHistory } from "react-router-dom";
-
-import './style.scss'
-
-import {
-    Card,
-    CardHeader,
-    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 CCommentsFeed from "./comments_feed";
-import CommentField from "./comment_field";
-
-
-export function CardPost({ postData }) {
-    // console.log('postData: ', 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 toAccount() {
-        history.push(`/user/${postData?.owner?._id}`)
-    }
-
-    function addCommentFocus() {
-        document.getElementById('addCommentField').focus()
-    }
-
-
-    return (
-        <Card
-            sx={{
-                display: 'flex',
-                flexDirection: 'column',
-                minHeight: '80vh',
-                borderRadius: 0
-            }}
-        >
-            <CardHeader
-                avatar={
-                    <Avatar
-                        alt={postData?.owner?.login}
-                        src={(url + postData?.owner?.avatar?.url)}
-                        sx={{
-                            width: 50,
-                            height: 50
-                        }}
-                    />
-                }
-
-                title={
-                    <Typography
-                        sx={{
-                            cursor: 'pointer'
-                        }}
-                        onClick={toAccount}
-                    >
-                        {postData?.owner?.login}
-                    </Typography>
-                }
-
-                subheader={dateofPostParse}
-            />
-
-            <Divider />
-
-            <CardContent
-                className='post-comments'
-
-                sx={{
-                    flex: '0 0 450px',
-                    overflowY: 'auto',
-                }}
-            >
-                <Grid
-                    container
-                    spacing={2}
-                    sx={{
-                        marginBottom: 3
-                    }}
-                >
-                    <Grid
-                        xs={2}
-                        sx={{
-                            flex: '0 0 45px'
-                        }}
-                    >
-                        <Avatar
-                            alt={postData?.owner?.login}
-                            src={(url + postData?.owner?.avatar?.url)}
-                            sx={{
-                                width: 40,
-                                height: 40
-                            }}
-                        />
-                    </Grid>
-
-                    <Grid
-                        xs={10}
-                    >
-                        <Typography
-                            variant="subtitle2"
-                            color="text.secondary"
-                            sx={{
-                                cursor: 'pointer',
-                                width: 'fit-content'
-                            }}
-                            onClick={toAccount}
-                        >
-                            {postData?.owner?.login}
-                        </Typography>
-
-                        <Typography
-                            variant="subtitle2"
-                            color="text.primary"
-                        >
-                            {postData?.title}
-                        </Typography>
-
-                        <Typography
-                            variant="body2"
-                            color="text.secondary"
-                        >
-                            {postData?.text}
-                        </Typography>
-                    </Grid>
-                </Grid>
-
-                <Grid>
-                    {/* <CommentsFeed data={postData?.comments} /> */}
-                    <CCommentsFeed />
-                </Grid>
-            </CardContent>
-
-            <Divider />
-
-            <CardActions disableSpacing>
-                <Box
-                    sx={{
-                        flexGrow: 1
-                    }}>
-                    <Grid
-                        xs={12}
-                        container
-                        justifyContent="space-between"
-                        alignItems="center"
-                        sx={{
-                            fontSize: '12px'
-                        }}
-                    >
-                        <Grid container>
-                            <Grid>
-                                <IconButton
-                                    aria-label="add to favorites"
-                                    onClick={() => (console.log('click like main post'))}
-                                >
-                                    <FavoriteBorderRounded />
-                                </IconButton>
-                            </Grid>
-
-                            <Grid>
-                                <IconButton
-                                    onClick={addCommentFocus}
-                                >
-                                    <ChatBubbleOutline />
-                                </IconButton>
-                            </Grid>
-
-                            <Grid>
-                                <IconButton
-                                    aria-label="share"
-                                >
-                                    <Send />
-                                </IconButton>
-                            </Grid>
-                        </Grid>
-
-                        <Grid>
-                            <IconButton>
-                                <TurnedInNot />
-                            </IconButton>
-                        </Grid>
-                    </Grid>
-
-                    <Grid container>
-                        <Typography
-                            variant="subtitle1"
-                            color="text.secondary"
-                            sx={{
-                                padding: 1
-                            }}
-                        >
-                            Нравится: {postData.likesCount ? postData.likesCount : '0'}
-                        </Typography>
-                    </Grid>
-                </Box>
-            </CardActions>
-
-            <Divider />
-
-            <CardActions>
-                <CommentField />
-            </CardActions>
-        </Card>
-    )
-}

+ 0 - 52
js/Project/project/src/components/post/исходники до теста с провайдером/carousel_post.js

@@ -1,52 +0,0 @@
-import Carousel from 'react-material-ui-carousel'
-import { Box } from '@mui/system';
-
-import { url } from '../../App';
-
-// карусель
-export function MyCarouselPost({ postImages }) {
-    // console.log('postImages: ', postImages)
-
-    return (postImages &&
-        <Carousel
-            autoPlay={false}
-            cycleNavigation={false}
-            animation={"slide"}
-            indicatorContainerProps={{
-                style: {
-                    marginTop: '-50px',
-                    zIndex: 999,
-                    position: 'inherit'
-                }
-            }}
-            className='karusel'
-
-            sx={{
-                width: '100%',
-            }}
-
-        >
-            {
-                postImages.map(item =>
-                    <Box
-                        sx={{
-                            paddingTop: '100%',
-                            backgroundSize: 'contain',
-                            backgroundColor: 'black',
-                            backgroundImage: `url(/images/noPhoto.png)`,
-                            backgroundRepeat: 'no-repeat',
-                            backgroundPosition: 'center',
-                        }}
-
-                        key={item?.url}
-
-                        style={item?.url &&
-                            { backgroundImage: `url(${url + item.url})` }
-                        }
-                    />
-                )
-            }
-        </Carousel >
-    )
-}
-

+ 0 - 216
js/Project/project/src/components/post/исходники до теста с провайдером/comment_card.js

@@ -1,216 +0,0 @@
-import { url } from "../../App";
-import { useHistory } from "react-router-dom";
-import { useState } from "react";
-
-import {
-    Avatar,
-    IconButton,
-    Typography,
-    Divider,
-} from '@mui/material'
-
-import Grid from '@mui/material/Unstable_Grid2';
-
-import { FavoriteBorderRounded } from '@mui/icons-material/'
-
-import './style.scss'
-
-
-function CommentCard({ data }) {
-    console.log('data: ', data)
-
-    const history = useHistory()
-
-    // дата поста
-    const dateofPost = new Date(+data?.createdAt)
-    const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
-    const dateofPostParse = `${dateofPost.getDate() < 10 ? '0' + dateofPost.getDate() : dateofPost.getDate()} 
-    ${months[dateofPost.getMonth()]} 
-    ${dateofPost.getFullYear()}
-    ${dateofPost.getHours() < 10 ? '0' + dateofPost.getHours() : dateofPost.getHours()}:${dateofPost.getMinutes() < 10 ? '0' + dateofPost.getMinutes() : dateofPost.getMinutes()}`
-
-
-    // let answerToCommentData
-    // скрываем/открываем вложенные комментарии
-    const [openComments, setOpenComments] = useState('Посмотреть ответы')
-    // скрываем/открываем блок с ответами на комментарии
-    const [toggleAnswerToBlock, setToggleAnswerToBlock] = useState(false)
-
-    // функция скрытия/открытия ответов на комменты
-    async function changeText(id) {
-        setToggleAnswerToBlock(!toggleAnswerToBlock)
-
-        return (openComments === 'Посмотреть ответы'
-            ? setOpenComments('Скрыть ответы')
-            : setOpenComments('Посмотреть ответы')
-        )
-    }
-
-    function toAccount(id) {
-        history.push(`/user/${id}`)
-    }
-
-    function addCommentFocus() {
-        const inputField = document.getElementById('addCommentField')
-
-        inputField.focus()
-    }
-
-
-    return (
-        <Grid
-            container
-            spacing={2}
-            sx={{
-                marginBottom: 2
-            }}
-        >
-            <Grid
-                xs={1.5}
-                sx={{
-                    flex: '0 0 45px'
-                }}
-            >
-                <Avatar
-                    alt={data?.owner?.login}
-                    src={(url + data?.owner?.avatar?.url)}
-                    sx={{
-                        width: 40,
-                        height: 40
-                    }}
-                />
-            </Grid>
-
-            <Grid
-                xs={10.5}
-                container
-            >
-                <Grid
-                    xs={10}
-                    sx={{
-                        paddingBottom: 0
-                    }}
-                >
-                    <Typography
-                        variant="subtitle2"
-                        color="text.secondary"
-                        sx={{
-                            cursor: 'pointer'
-                        }}
-                        onClick={() => toAccount(data?.owner?._id)}
-                    >
-                        {data?.owner?.login}
-                    </Typography>
-
-                    <Typography
-                        variant="body2"
-                        color="text.secondary"
-                    >
-                        {data?.text}
-                    </Typography>
-                </Grid>
-
-                <Grid
-                    xs={2}
-                    sx={{
-                        paddingBottom: 0
-                    }}
-                >
-                    <IconButton
-                        onClick={() => (console.log('click like comment'))}
-                    >
-                        <FavoriteBorderRounded
-                            fontSize='small'
-                        />
-                    </IconButton>
-                </Grid>
-
-                <Grid
-                    xs={5.5}
-                    sx={{
-                        padding: 0,
-                        paddingLeft: 1
-
-                    }}
-                >
-                    <Typography
-                        variant="caption"
-                        color="text.disabled"
-                        align='left'
-                    >
-                        {dateofPostParse}
-                    </Typography>
-                </Grid>
-
-                <Grid
-                    xs={3.5}
-                    sx={{
-                        padding: 0
-                    }}
-                >
-                    <Typography
-                        variant="caption"
-                        color="text.disabled"
-                        align='left'
-                    >
-                        Нравится: {data?.likesCount || '0'}
-                    </Typography>
-                </Grid>
-
-                <Grid
-                    xs={3}
-                    sx={{
-                        padding: 0,
-                        paddingRight: 1
-                    }}
-                >
-                    <Typography
-                        variant="caption"
-                        color="text.disabled"
-                        align='left'
-                        sx={{
-                            cursor: 'pointer'
-                        }}
-                        onClick={addCommentFocus}
-                    >
-                        Ответить
-                    </Typography>
-                </Grid>
-
-                <Grid
-                    xs={12}
-                    sx={{
-                        padding: '0 8px'
-                    }}
-                >
-                    {(data?.answers.length !== 0) &&
-                        <Divider>
-                            <Typography
-                                variant="caption"
-                                color="text.disabled"
-                                sx={{
-                                    cursor: 'pointer'
-                                }}
-                                onClick={() => changeText(data?._id)}
-                            >
-                                {openComments}
-                            </Typography>
-                        </Divider>}
-                </Grid>
-
-                {toggleAnswerToBlock &&
-                    <Grid xs={12}
-                        sx={{
-                            padding: '0 8px'
-                        }}>
-                        {data?.answers && (data?.answers).map(item => <CommentCard
-                            data={item}
-                            key={item._id}
-                        />)}
-                    </Grid>}
-            </Grid>
-        </Grid >
-    )
-}
-
-export default CommentCard

+ 0 - 138
js/Project/project/src/components/post/исходники до теста с провайдером/comment_field.js

@@ -1,138 +0,0 @@
-import { url } from "../../App";
-
-import './style.scss'
-
-import {
-    IconButton,
-    Typography,
-    Box,
-    TextField,
-    Popover
-} from '@mui/material'
-import {
-    SentimentSatisfiedAlt
-} from '@mui/icons-material/'
-
-import EmojiPicker from 'emoji-picker-react';
-
-import { useState } from "react";
-import { useDispatch } from "react-redux";
-
-import { actionFullAddComment } from "../redux/thunks";
-import { useParams } from "react-router-dom";
-
-
-function CommentField() {
-    // console.log('id: ', id)
-
-    const { postId } = useParams()
-    const dispatch = useDispatch()
-
-    // отслеживаем состояние поля ввода комментария для поста
-    const [comment, setComment] = useState({ text: '', post: { _id: postId } })
-    console.log('params for new comment: ', comment)
-
-    // открытие поповера emoji
-    const [openPopover, setOpenPopover] = useState(null);
-    const openEmoji = (event) => {
-        setOpenPopover(event.currentTarget);
-    }
-    const closeEmoji = () => {
-        setOpenPopover(null);
-    }
-    const open = Boolean(openPopover);
-    const emojiField = open ? 'simple-popover' : undefined;
-
-
-    function uploadComment() {
-        // отправляем запрос на создание комментария
-        dispatch(actionFullAddComment(comment, postId))
-
-        // и чистим поле ввода комментария
-        setComment({ ...comment, text: '' })
-    }
-
-    return (
-        <Box
-            sx={{
-                display: 'flex',
-                alignItems: 'flex-end',
-                justifyContent: 'space-between',
-                width: '100%'
-            }}
-        >
-            <TextField
-                id='addCommentField'
-                variant="standard"
-                multiline
-                placeholder='Комментировать...'
-                size="small"
-                color="info"
-                sx={{
-                    margin: 1,
-                    width: '100%',
-                }}
-                value={comment.text}
-                onChange={e => setComment({ ...comment, text: e.target.value })}
-
-                InputProps={{
-                    startAdornment:
-                        <Box>
-                            <IconButton
-                                aria-describedby={emojiField}
-                                onClick={openEmoji}
-                            >
-                                <SentimentSatisfiedAlt
-                                    fontSize='small'
-                                />
-                            </IconButton>
-
-                            <Popover
-                                id={emojiField}
-                                open={open}
-                                anchorEl={openPopover}
-                                onClose={closeEmoji}
-                                anchorOrigin={{
-                                    vertical: 'top',
-                                    horizontal: 'right',
-                                }}
-                                transformOrigin={{
-                                    vertical: 'bottom',
-                                    horizontal: 'left',
-                                }}
-                            >
-                                <EmojiPicker
-                                    searchPlaceHolder='Найти'
-                                    emojiStyle='apple'
-                                    width={300}
-                                    height={450}
-                                    onEmojiClick={
-                                        e => setComment({ ...comment, text: comment.text + e.emoji })
-                                    }
-                                />
-                            </Popover>
-                        </Box>,
-
-                    endAdornment:
-                        <Box>
-                            {comment.text !== '' && <Typography
-                                variant='subtitle2'
-                                color="primary.main"
-                                align='center'
-                                sx={{
-                                    alignSelf: 'center',
-                                    cursor: 'pointer',
-                                    margin: '0 8px'
-                                }}
-                                onClick={uploadComment}
-                            >
-                                Отправить
-                            </Typography>}
-                        </Box>
-                }}
-            />
-        </Box>
-    )
-}
-
-export default CommentField

+ 0 - 62
js/Project/project/src/components/post/исходники до теста с провайдером/comments_feed.js

@@ -1,62 +0,0 @@
-import { url } from "../../App";
-import { useParams } from "react-router-dom";
-import { useEffect } from "react";
-import { connect, useSelector } from "react-redux";
-
-import { actionFullFindCommentsPostOne } from "../redux/thunks";
-
-import CommentCard from "./comment_card";
-
-import './style.scss';
-
-
-function CommentsFeed({ comments = [], loadComments }) {
-    // console.log('comments: ', comments)
-
-    const { postId } = useParams()
-    const addNewComment = useSelector(state => state?.promise?.CreateComment?.payload)
-    // console.log('test: ', addNewComment)
-
-    // перезагрузка ленты комментариев на посте, если только зашли на сраницу или отправили новый коммент
-    useEffect(() => { loadComments(postId) }, [postId, addNewComment])
-
-
-    // фуyкция построения дерева комментариев
-    function commentsTreeConstructor(arr, answerToId = null) {
-        const result = []
-
-        for (const commentItem of arr) {
-            if (commentItem.answerTo && commentItem.answerTo._id === answerToId) {
-                commentItem.answers = commentsTreeConstructor(arr, commentItem._id)
-                result.push(commentItem)
-            }
-
-            if (!commentItem.answerTo && !answerToId) {
-                commentItem.answers = commentsTreeConstructor(arr, commentItem._id)
-                result.push(commentItem)
-            }
-        }
-
-        // сортируем от новых к старым
-        return result.sort((x, y) => y.createdAt - x.createdAt)
-    }
-
-    const data = commentsTreeConstructor(comments)
-
-
-    return (
-        <>
-            {data && data.map(item => <CommentCard
-                data={item}
-                key={item._id}
-            />
-            )}
-        </>
-    )
-
-}
-
-const CCommentsFeed = connect(state => ({ comments: state?.promise?.FindComments?.payload }), { loadComments: actionFullFindCommentsPostOne })(CommentsFeed)
-
-
-export default CCommentsFeed

+ 0 - 55
js/Project/project/src/components/post/исходники до теста с провайдером/index.js

@@ -1,55 +0,0 @@
-import { useEffect } from 'react';
-import { useParams } from 'react-router-dom';
-import { connect } from 'react-redux';
-
-import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
-import { Paper, Box } from '@mui/material';
-import { styled } from '@mui/material/styles';
-
-import { actionFindPostOne } from '../redux/action';
-import { MyCarouselPost } from './carousel_post';
-import { CardPost } from './card_post';
-
-
-const Item = styled(Paper)(() => ({
-    borderRadius: 0,
-    boxShadow: 'none',
-}))
-
-
-function Comments({ post = {}, loadPost }) {
-    // console.log('post: ', post)
-
-    const { postId } = useParams()
-    useEffect(() => { loadPost(postId) }, [postId])
-
-    return (
-        <Box>
-            <Grid2 container sx={{
-                height: '80vh',
-            }}>
-                <Grid2 xs={7.5}>
-                    <Item sx={{
-                        backgroundColor: "black",
-                        width: '100%',
-                        height: '100%',
-                        display: 'flex',
-                        alignItems: 'center'
-                    }}>
-                        <MyCarouselPost postImages={post?.images} />
-                    </Item>
-                </Grid2>
-                <Grid2 xs={4.5}>
-                    <Item >
-                        <div style={{ backgroundColor: "yellow" }}>
-                            <CardPost postData={post} />
-                        </div>
-                    </Item>
-                </Grid2>
-            </Grid2>
-        </Box>
-    )
-}
-
-export const CComments = connect(state => ({ post: state?.promise?.PostFindOne?.payload }), { loadPost: actionFindPostOne })(Comments)
-

+ 0 - 3
js/Project/project/src/components/post/исходники до теста с провайдером/style.scss

@@ -1,3 +0,0 @@
-.post-comments::-webkit-scrollbar {
-    width: 0;
-}

+ 1 - 1
js/Project/project/src/components/structure/footer.js

@@ -9,7 +9,7 @@ function Footer() {
                     gutterBottom
                     textAlign='center'
                     sx={{
-                        mt: 10, // тут каждая единица - это 8px
+                        mt: 10,
                         color: 'inherit',
                     }}>
                     Created by @Volddemar4ik

+ 0 - 194
js/Project/project/src/components/structure/header(old).js

@@ -1,194 +0,0 @@
-import { store } from '../redux';
-import React, { createContext, useContext, useState } from 'react';
-import { useHistory, useLocation } from 'react-router-dom';
-import { connect, useSelector } from 'react-redux';
-
-import { AppBar, Box, Toolbar, Typography, Menu, Container, Avatar, Tooltip, MenuItem, IconButton, Stack } from '@mui/material'
-import { Instagram, AddAPhotoRounded, Search } from '@mui/icons-material';
-// import { actionFullLogout } from '../redux/action';
-import { actionFullLogout } from '../redux/thunks';
-
-import { url } from '../../App';
-
-
-function UserMenu() {
-    let history = useHistory()
-    const location = useLocation().pathname
-    const user = useContext(UserContext)
-
-    // потому что запрос на логин не отдает аватар, а только id и login
-    const userData = useSelector(state => state?.promise?.AboutMe?.payload)
-
-    const [anchorElUser, setAnchorElUser] = useState(null)
-
-    // отображение/скрытие меню на аватарке
-    const handleOpenUserMenu = (event) => {
-        setAnchorElUser(event.currentTarget)
-    }
-    const handleCloseUserMenu = () => {
-        setAnchorElUser(null)
-    }
-
-    // Thunk для разлогина и редирект на главную, потому что остальные запросы не работают, если нет токена
-    async function onLogout() {
-
-        const result = await store.dispatch(actionFullLogout())
-
-        // проверяем, реально ли очистили токен (и все хранилище)
-        if (Object.keys(result).length === 0 && location != '/') {
-            history.push('/')
-        }
-    }
-
-
-    return (
-        <Stack
-            direction='row'
-        >
-            <IconButton
-                sx={{
-                    color: '#000',
-                    transform: 'scale(-1, 1)'
-                }}
-                onClick={() => {
-                    history.push('/search')
-                }}
-            >
-                <Search />
-            </IconButton>
-
-            <IconButton
-                sx={{
-                    color: '#000'
-                }}
-                onClick={() => {
-                    history.push('/createpost')
-                }}
-            >
-                <AddAPhotoRounded />
-            </IconButton>
-
-            <Box sx={{ flexGrow: 0 }}>
-                <Tooltip>
-                    <IconButton
-                        onClick={handleOpenUserMenu}
-                    >
-                        <Avatar
-                            alt={user?.login}
-                            src={url + userData?.avatar?.url}
-                        />
-                    </IconButton>
-                </Tooltip>
-
-                <Menu
-                    sx={{
-                        mt: '50px'
-                    }}
-                    id="menu-appbar"
-                    anchorEl={anchorElUser}
-                    anchorOrigin={{
-                        vertical: 'top',
-                        horizontal: 'right',
-                    }}
-                    keepMounted
-                    transformOrigin={{
-                        vertical: 'top',
-                        horizontal: 'right',
-                    }}
-                    open={Boolean(anchorElUser)}
-                    onClose={handleCloseUserMenu}
-                >
-
-                    <MenuItem
-                        onClick={() => {
-                            history.push(`/user/${user?.id}`)
-                        }}
-                    >
-                        <Typography
-                            textAlign="center">
-                            <span>
-                                {user?.login}
-                            </span>
-                        </Typography>
-                    </MenuItem>
-
-                    <MenuItem
-                        onClick={onLogout}
-                    >
-                        <Typography
-                            textAlign="center"
-                        >
-                            <span>
-                                Выйти
-                            </span>
-                        </Typography>
-                    </MenuItem>
-                </Menu>
-            </Box>
-        </Stack>
-    )
-}
-
-
-const UserContext = createContext()
-
-function Header({ login }) {
-    let history = useHistory()
-
-    function toMain() {
-        history.push('/');
-    }
-
-    return (
-        <AppBar
-            position='sticky'
-            color='inherit'
-        >
-            <UserContext.Provider value={login}>
-                <Container
-                    maxWidth="xl"
-                >
-                    <Toolbar
-                        disableGutters
-                    >
-                        <Stack
-                            direction='row'
-                            sx={{
-                                display: 'flex',
-                                flexGrow: '1'
-                            }}
-                        >
-                            <IconButton
-                                edge='start'
-                                sx={{
-                                    color: '#000'
-                                }}
-                                onClick={toMain}
-                            >
-                                <Instagram />
-
-                                <Typography
-                                    variant="h6"
-                                    sx={{
-                                        fontFamily: 'monospace',
-                                        fontWeight: 700,
-                                        letterSpacing: '.3rem',
-                                        ml: 1
-                                    }}
-                                >
-                                    Hipstagram
-                                </Typography>
-                            </IconButton>
-                        </Stack>
-
-                        {/* {login && <UserMenu login={login} />} */}
-                        {login && <UserMenu />}
-
-                    </Toolbar>
-                </Container>
-            </UserContext.Provider>
-        </AppBar>
-    )
-}
-
-export const CHeader = connect(state => ({ login: state?.auth?.payload?.sub }))(Header)

+ 34 - 17
js/Project/project/src/components/structure/header.js

@@ -28,7 +28,7 @@ const StyledInputBase = styled(TextField)(({ theme }) => ({
     },
 }))
 
-// Тень на хетере при прокручивании
+// Тень на хедере при прокручивании
 function ElevationScroll(props) {
     const { children, window } = props;
     const trigger = useScrollTrigger({
@@ -63,12 +63,9 @@ function UserMenu({ user }) {
 
     // Thunk для разлогина и редирект на главную, потому что остальные запросы не работают, если нет токена
     async function onLogout() {
-        const res = await dispatch(actionFullLogout())
+        dispatch(actionFullLogout())
 
-        // проверяем, реально ли очистили токен (и все хранилище)
-        if (Object.keys(res).length === 0 && !location) {
-            history.push('/')
-        }
+        history.push('/')
     }
 
     function toCreatepost() {
@@ -82,7 +79,10 @@ function UserMenu({ user }) {
 
     return (
         <Box sx={{ flexGrow: 0 }}>
-            <Tooltip title='Открыть меню'>
+            <Tooltip
+                title='Меню пользователя'
+                placement="bottom-end"
+            >
                 <IconButton onClick={handleOpenUserMenu}>
                     <Avatar
                         alt={user?.login}
@@ -155,12 +155,25 @@ function UserMenu({ user }) {
 function Header({ login }) {
     let history = useHistory()
 
+    // отслеживание введенного текста в поиске
     const [searchText, setSearchText] = useState('')
 
+    // функция поиска
+    function toSearch() {
+
+
+        history.push('/search')
+    }
+
+
+
+
+
     function toMain() {
         history.push('/');
     }
 
+
     return (
         <React.Fragment>
             <ElevationScroll {...login}>
@@ -242,17 +255,21 @@ function Header({ login }) {
                                                         fontWeight: '700'
                                                     }}>
                                                     {searchText !== '' &&
-                                                        <Button
-                                                            startIcon={<TouchAppRounded />}
-                                                            color="secondary"
-                                                            size="small"
-                                                            variant="elevated"
-                                                            sx={{
-                                                                padding: '5px 10px'
-                                                            }}
+                                                        <Box
+                                                            onClick={toSearch}
                                                         >
-                                                            Искать
-                                                        </Button>}
+                                                            <Button
+                                                                startIcon={<TouchAppRounded />}
+                                                                color="primary"
+                                                                size="small"
+                                                                variant="filledTonal"
+                                                                sx={{
+                                                                    padding: '5px 10px'
+                                                                }}
+                                                            >
+                                                                Искать
+                                                            </Button>
+                                                        </Box>}
                                                 </Typography>
                                         }}
                                     />

+ 112 - 101
js/Project/project/src/components/structure/modal.js

@@ -1,14 +1,17 @@
 import { useContext } from "react";
 import { useDispatch, useSelector } from "react-redux";
-import { useHistory } from "react-router-dom";
+import { useHistory, useLocation, useParams } from "react-router-dom";
 
-import { Typography, Box, Stack, Backdrop, Modal, Fade, Divider, IconButton, Avatar, Button, CardHeader } from '@mui/material'
+import { Typography, Box, Stack, Backdrop, Modal, Fade, Divider, IconButton, Avatar, CardHeader } from '@mui/material'
+import Button from '@mui/material-next/Button';
 import { Close } from '@mui/icons-material'
 
 import { ModalForCountsContext, UpdateProfile } from "../../App";
 import { actionFullUpdateProfile } from "../../redux/thunks";
 import { url } from "../../App"
 
+import './style.scss'
+
 
 const style = {
     position: 'absolute',
@@ -25,13 +28,106 @@ const style = {
 };
 
 
-export function ModalWindow() {
+
+// Компонент карточка юзера с кнопкой подписки/отписки (нужно делать всегда двойную проверку data.owner.id || data.id (идет вызов модалки на комменте || посте), потому что у нас разные сущности на лайках и на подписчиках)
+export function RecommendedCard({ data }) {
+    // console.log('data MODAL: ', data)
+
+    const history = useHistory()
+    const dispatch = useDispatch()
+
+    // контекст обновления профиля
+    const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
+    // console.log('updateProfile: ', updateProfile)
+
+    // контекст модального окна
+    const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
+
+    // проверка, является ли пользователь моим подписчиком
+    const myData = useSelector(state => state?.promise?.AboutMe?.payload)
+    const isFollowing = myData?.following && (myData?.following).some(item => item._id === (data?.owner?._id || data?._id))
+
+    // проверка текущего id страницы
+    const currentId = useLocation().pathname.split('/')[useLocation().pathname.split('/').length - 1] // test
+    // console.log('currentId: ', currentId) // test
+
+    // мой id
+    const myid = myData?._id
+
+    // переход на аккаунт
+    function toAccount() {
+        history.push(data?.owner?._id ? `/user/${data?.owner?._id}` : `/user/${data?._id}`)
+        setModalArray(false)
+    }
+
+    // функция подписки/отписки
+    function isSubscribing() {
+        // console.log('old data: ', updateProfile)
+
+        // при клике на посте
+        const newData = {
+            ...updateProfile, following: (isFollowing
+                ? updateProfile.following.filter(item => item._id !== (data?.owner?._id || data?._id)) // отписка
+                : [...updateProfile.following, { _id: (data?.owner?._id || data?._id) }] // подписка
+            )
+        }
+
+        // console.log('new data: ', newData)
+        dispatch(actionFullUpdateProfile(newData, currentId))
+    }
+
+    return (
+        <CardHeader
+            avatar={
+                <Avatar
+                    src={url + (data?.owner?.avatar?.url || data?.avatar?.url)}
+                    aria-label="recipe"
+                    sx={{ width: 36, height: 36 }}
+                />
+            }
+
+            title={
+                < Typography
+                    sx={{
+                        cursor: 'pointer',
+                        width: 'fit-content'
+                    }}
+                    variant="subtitle2"
+                    color='text.secondary'
+                    onClick={toAccount}
+                >
+                    {data?.owner?.login || data?.login || 'anon user'}
+                </Typography >
+            }
+
+            action={//сначала проверка на меня. если не я, рисуем кнопку
+                ((myData?.following && (data?.owner?._id || data?._id) != myid)) && <Box
+                    onClick={isSubscribing}
+                >
+                    <Button
+                        variant={isFollowing ? "elevated" : "filledTonal"}
+                        size="small"
+                        sx={{
+                            minWidth: '140px',
+                            padding: '5px 10px'
+                        }}
+                    >
+                        {(isFollowing ? 'Отписаться' : 'Подписаться')}
+                    </Button>
+                </ Box>
+            }
+        />
+    )
+}
+
+
+function ModalWindow() {
 
     // контекст модального окна
     const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
 
     // контекст обновления профиля
-    const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
+    // const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
     // console.log('test context: ', updateProfile)
 
 
@@ -87,14 +183,22 @@ export function ModalWindow() {
                         <Box
                             className='recommendedBox'
                             sx={{
-                                margin: '8px 0',
+                                margin: '8px 0 8px 8px',
                                 width: '95%',
-                                paddingLeft: '8px',
                                 minHeight: 'fit-content',
                                 maxHeight: 350,
                                 overflowY: 'auto'
                             }}>
-                            {modalArray.map(item => <RecommendedCard key={item._id} data={item} />)}
+                            {modalArray.length != 0
+                                ? modalArray.map(item => <RecommendedCard key={item._id} data={item} />)
+                                : <Typography
+                                    variant="subtitle1"
+                                    align='center'
+                                    color='text.secondary'
+                                    gutterBottom
+                                >
+                                    Пока-что здесь никого нет...
+                                </Typography>}
                         </Box>
                     </Box>
                 </Fade>
@@ -103,97 +207,4 @@ export function ModalWindow() {
     )
 }
 
-
-
-// Компонент карточка юзера с кнопкой подписки/отписки (нужно делать всегда двойную проверку data.owner.id || data.id, потому что у нас разные сущности на лайках и на подписчиках)
-function RecommendedCard({ data }) {
-    console.log('dataIncard: ', data)
-
-    const history = useHistory()
-    const dispatch = useDispatch()
-
-    // контекст обновления профиля
-    const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
-    console.log('updateProfile: ', updateProfile)
-
-    // контекст модального окна
-    const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
-
-    // проверка, является ли пользователь моим подписчиком
-    const myData = useSelector(state => state?.promise?.AboutMe?.payload)
-    const isFollowing = myData?.following && (myData?.following).some(item => item._id === (data?.owner?._id || data?._id))
-
-    // мой id
-    const myid = myData?._id
-
-    // переход на аккаунт
-    function toAccount() {
-        history.push(data?.owner?._id ? `/user/${data?.owner?._id}` : `/user/${data?._id}`)
-        setModalArray(modalArray.length = 0)
-    }
-
-
-    // какого-то хуя не работает((
-    function isSubscribing() {
-        console.log('подписка или отписка при клике на юзера в модальном окне')
-
-        if (isFollowing) {
-
-            console.log('updateProfile START: ', updateProfile)
-
-            setUpdateProfile({ ...updateProfile, following: updateProfile.following.filter(item => item?._id !== data?.owner?._id) })
-
-        } else {
-            setUpdateProfile({ ...updateProfile, following: [...updateProfile.following, { _id: data.owner._id }] })
-        }
-
-        // 
-        console.log('updateProfile END: ', updateProfile)
-        dispatch(actionFullUpdateProfile(updateProfile))
-    }
-
-    return (
-        <CardHeader
-            avatar={
-                <Avatar
-                    src={url + (data?.owner?.avatar?.url || data?.avatar?.url)}
-                    aria-label="recipe"
-                    sx={{ width: 36, height: 36 }}
-                />
-            }
-
-            // action={
-            //     <Button
-            //         variant="text"
-            //         size="small"
-            //         onClick={() => console.log('подписка или отписка при клике на юзера в модальном окне')}
-            //     >
-            //         {isFollowing ? 'Отписаться' : 'Подписаться'}
-            //     </Button>
-            // }
-            action={ //сначала проверка на меня. если не я, рисуем кнопку
-                ((data?.owner?._id || data?._id) != myid) && <Button
-                    variant="text"
-                    size="small"
-                    onClick={isSubscribing}
-                >
-                    {isFollowing ? 'Отписаться' : 'Подписаться'}
-                </Button>
-            }
-
-            title={
-                <Typography
-                    sx={{
-                        cursor: 'pointer',
-                        width: 'fit-content'
-                    }}
-                    variant="subtitle2"
-                    color='text.secondary'
-                    onClick={toAccount}
-                >
-                    {data?.owner?.login || data?.login || 'anon user'}
-                </Typography>
-            }
-        />
-    )
-}
+export default ModalWindow

+ 5 - 0
js/Project/project/src/components/structure/style.scss

@@ -0,0 +1,5 @@
+.MuiCardHeader-root {
+    .css-sgoict-MuiCardHeader-action {
+        display: contents;
+    }
+}

+ 9 - 0
js/Project/project/src/components/update_profile/index.js

@@ -0,0 +1,9 @@
+function UpsertProfile() {
+    return (
+        <div>
+            редактируем профиль здесь
+        </div>
+    )
+}
+
+export default UpsertProfile

+ 0 - 175
js/Project/project/src/components/user/gallery(test).js

@@ -1,175 +0,0 @@
-import * as React from 'react';
-import { useEffect } from 'react';
-
-import ImageList from '@mui/material/ImageList';
-import ImageListItem from '@mui/material/ImageListItem';
-import { Box } from '@mui/system';
-
-import AutoAwesomeMotionRoundedIcon from '@mui/icons-material/AutoAwesomeMotionRounded';
-import { useParams } from 'react-router';
-import { connect } from 'react-redux';
-// import { actionFullUserFindOne } from '../redux/action';
-import { actionFullUserFindOne } from '../redux/thunks';
-
-
-function StandardImageList({ userGallery = [], loadUserGallery }) {
-    // console.log('userGallery: ', userGallery)
-
-    const { userId } = useParams()
-    useEffect(() => { loadUserGallery(userId) }, [userId])
-
-
-    const clicker = (name) => {
-        // console.log('click: ', name)
-    }
-
-
-
-    return (
-        <ImageList sx={{ width: "100%", minHeight: "100%" }} cols={3} rowHeight={'auto'} >
-            {
-                itemData.map((item) => (
-                    // <ImageListItem key={item.img}>
-                    <ImageListItem key={Math.random()} >
-
-                        <AutoAwesomeMotionRoundedIcon sx={{
-                            color: 'white',
-                            transform: 'scale(-1, 1)',
-                            position: 'absolute',
-                            right: '10px',
-                            top: '10px',
-                            fontSize: '32px'
-                        }} />
-
-                        <Box sx={{
-                            width: '100%',
-                            paddingTop: '100%',
-                            backgroundSize: 'cover',
-                            backgroundColor: 'black',
-                            backgroundRepeat: 'no-repeat'
-                        }}
-
-                            style={{ backgroundImage: `url(${item.img})` }}
-
-                            // тут поставить ссылку на пост, к которому привязаны картинки
-                            onClick={() => { clicker(item.title) }}
-
-                        >
-
-
-                            {/* <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>
-                ))
-            }
-        </ImageList >
-    );
-}
-
-
-
-export const CStandardImageList = connect(state => ({ userGallery: state?.promise?.Feed?.payload }), { loadUserGallery: actionFullUserFindOne })(StandardImageList)
-
-// массив с картинками
-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',
-    }
-];

+ 6 - 5
js/Project/project/src/components/user/gallery.js

@@ -1,13 +1,15 @@
+import { url } from '../../App';
+
 import * as React from 'react';
 import { connect } from 'react-redux';
 import { useHistory } from 'react-router-dom';
 
 import { Box } from '@mui/system';
-import { ImageList, ImageListItem } from '@mui/material';
+import { IconButton, ImageList, ImageListItem, Typography } from '@mui/material';
 
-import { AutoAwesomeMotionRounded } from '@mui/icons-material';
+import { AutoAwesomeMotionRounded, ReplyRounded } from '@mui/icons-material';
 
-import { url } from '../../App';
+import './style.scss'
 
 
 export default function StandardImageList({ images }) {
@@ -30,7 +32,7 @@ export default function StandardImageList({ images }) {
         >
             {images.map(item => (
 
-                < ImageListItem
+                <ImageListItem
                     key={item?._id}
                 >
                     {/* иконка галереи на посте в ленте */}
@@ -73,5 +75,4 @@ export default function StandardImageList({ images }) {
     )
 }
 
-
 export const СStandardImageList = connect(state => ({ images: state?.feed?.UserFeed?.payload }))(StandardImageList)

+ 14 - 32
js/Project/project/src/components/user/index.js

@@ -7,53 +7,35 @@ import { store } from '../../redux';
 import { Box, Container, Divider } from '@mui/material';
 
 import BasicCard from './userData';
-// import { actionFindUserOne, actionFullUserFindOne } from '../redux/action';
 import { actionFindUserOne, actionFullUserFindOne } from '../../redux/thunks';
 
 import { StandardImageList, СStandardImageList } from './gallery';
 
 function User({ user = {}, loadUser }) {
-
+    // console.log('user: ', user)
 
     const { userId } = useParams()
     useEffect(() => { loadUser(userId) }, [userId])
 
-
-    // собираем все _id подписчиков в один массив - !!!!!!!!!!!!!!!!!!!!!! РАБОТАЕТ НЕКОРРЕКТНО
-    const promiceStatus = store.getState()?.promise?.UserFindOne?.status
-    const followingsArr = []
-
-    if (promiceStatus === 'FULFILLED' && user?.following) {
-        // console.log(999, user.following)
-        for (let id of (user.following)) {
-            for (let [key, value] of Object.entries(id)) {
-                if (key === '_id') {
-                    followingsArr.push(value)
-                }
-            }
-        }
-    }
-
     return (
         <React.Fragment>
-            <Container sx={{ minHeight: '100vh' }} maxWidth="100%" >
-                <Box sx={{ margin: "30px 50px" }}>
-                    <BasicCard userData={user} />
-                </Box>
-
-                <Divider />
-
-                <Box >
-                    {/* <StandardImageList /> */}
-                    <СStandardImageList />
-                </Box>
-            </Container>
+            <Box
+                sx={{
+                    margin: "20px 50px"
+                }}
+            >
+                <BasicCard userData={user} />
+            </Box>
+
+            <Divider />
+
+            <Box >
+                <СStandardImageList />
+            </Box>
         </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: actionFullUserFindOne })(User)
 
 

+ 0 - 0
js/Project/project/src/components/user/style.scss


+ 130 - 73
js/Project/project/src/components/user/userData.js

@@ -1,102 +1,155 @@
-import * as React from 'react';
-import { useParams } from 'react-router-dom';
-import { useSelector } from 'react-redux';
+import { url } from "../../App"
 
-import { CardContent, Typography, Button, Stack, Avatar, Box, Container } from '@mui/material';
-import { ManageAccountsOutlined, PersonRemoveRounded, PersonAddRounded } from '@mui/icons-material';
-import PersonRemoveRoundedIcon from '@mui/icons-material/PersonRemoveRounded';
+import React, { useContext } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
 
-import { url } from "../../App"
+import { CardContent, Typography, Stack, Avatar, Box, Container } from '@mui/material';
+import Button from '@mui/material-next/Button';
+import { PersonRemoveRounded, PersonAddRounded, ManageAccountsRounded } from '@mui/icons-material';
+import { actionUserPageSubscribing } from "../../redux/thunks";
 
+import ModalWindow from '../structure/modal';
+import { ModalForCountsContext, UpdateProfile } from "../../App";
 
-// блок пользовательских данных
-export default function BasicCard({ userData }) {
-    const urlAvatar = url + userData?.avatar?.url
 
-    // определяем количество постов пользоваеля
-    const userPostsCount = useSelector(state => state?.promise?.UserPostsCount?.payload)
+
+// Функция отображения кнопки редактирования своего профиля/отписки или подписки на другого юзера
+function UptadeProfileButton({ data }) {
+    // console.log('UserData: ', data)
+
+    const history = useHistory()
+    const { userId } = useParams()
+    const dispatch = useDispatch()
+
+    // контекст обновления профиля
+    const [updateProfile, setUpdateProfile] = useContext(UpdateProfile)
 
     // определяем мой id
-    const myId = useSelector(state => state?.auth?.payload?.sub?.id)
+    const myId = updateProfile?._id
+
     // определяем всех моих подписчиков
-    const myFollowingList = (useSelector(state => state?.promise?.AboutMe?.payload?.following))?.map(user => user._id)
+    const myFollowingList = updateProfile?.following?.map(user => user?._id)
 
-    function userFollowers() {
-        console.log('click on Followers')
-    }
+    // проверка, является ли пользователь моим подписчиком
+    const isFollowing = myFollowingList && (myFollowingList)?.some(item => item === (data?.owner?._id || data?._id))
 
-    function userFollowing() {
-        console.log('click on Following')
-    }
 
+    // функция подписки/отписки
+    function isSubscribing() {
+
+        // при клике на посте
+        const newData = {
+            ...updateProfile, following: (isFollowing
+                ? updateProfile.following.filter(item => item._id !== (data?.owner?._id || data?._id)) // отписка
+                : [...updateProfile.following, { _id: (data?.owner?._id || data?._id) }] // подписка
+            )
+        }
+
+        dispatch(actionUserPageSubscribing(newData, userId))
+    }
 
-    // Функция отображения кнопки редактирования своего профиля/отписки или подписки на другого юзера
-    function UptadeProfileButton() {
+    // функция перехода на редактирование профиля
+    function upsertProfile() {
+        history.push('/updateprofile')
+    }
 
-        // вырезаем ид юзера из адресной строки
-        const { userId } = useParams()
 
-        if (userId === myId) {
-            return (
+    return (
+        <React.Fragment>
+            {userId === myId && <Box
+                onClick={upsertProfile}
+            >
                 <Button
+                    size="large"
+                    variant="elevated"
                     sx={{
-                        minWidth: '300px',
-                        backgroundColor: '#ebebeb',
-                        border: 'none'
+                        minWidth: '300px'
                     }}
-                    color='inherit'
-                    variant="outlined"
                     startIcon={
-                        <ManageAccountsOutlined
-                            color='inherit'
-                        />}
+                        <ManageAccountsRounded />
+                    }
                     disableRipple
-                    onClick={() => console.log('Нажал редактировать профиль')}
                 >
-                    Редактировать аккаунт
+                    <Typography
+                        variant='button'
+                        display='block'
+                    >
+                        Редактировать аккаунт
+                    </Typography>
                 </Button>
-            )
-        }
-
-        if (myFollowingList.includes(userId)) {
-            return (
+            </Box>}
+            {myFollowingList?.includes(userId) && <Box
+                onClick={isSubscribing}
+            >
                 <Button
+                    size="large"
+                    variant="elevated"
                     sx={{
-                        minWidth: '300px',
-                        backgroundColor: '#ebebeb',
-                        border: 'none'
+                        minWidth: '300px'
                     }}
-                    color='inherit'
-                    variant="outlined"
                     startIcon={
-                        <PersonRemoveRounded
-                            color='inherit'
-                        />}
-                    disableRipple
-                    onClick={() => console.log('Нажал отписаться')
+                        <PersonRemoveRounded />
                     }
+                    disableRipple
                 >
-                    Отменить подписку
+                    <Typography
+                        variant='button'
+                        display='block'
+                    >
+                        Отписаться
+                    </Typography>
                 </Button>
-            )
-        } else {
-            return (
+            </Box>}
+            {(userId !== myId && !myFollowingList?.includes(userId)) && <Box
+                onClick={isSubscribing}
+            >
                 <Button
+                    size="large"
+                    variant="filledTonal"
                     sx={{
                         minWidth: '300px'
                     }}
                     startIcon={
                         <PersonAddRounded />
                     }
-                    variant="contained"
                     disableRipple
-                    onClick={() => console.log('Нажал подписаться')
-                    }
                 >
-                    Подписаться
+                    <Typography
+                        variant='button'
+                        display='block'
+                    >
+                        Подписаться
+                    </Typography>
                 </Button>
-            )
-        }
+            </Box>}
+        </React.Fragment>
+
+    )
+}
+
+
+
+// блок пользовательских данных
+export default function BasicCard({ userData }) {
+    // console.log('userData: ', userData)
+
+    // определяем количество постов пользоваеля
+    const userPostsCount = useSelector(state => state?.promise?.UserPostsCount?.payload)
+
+    // контекст модального окна
+    const [modalName, setModalName, modalArray, setModalArray, openModal, setOpenModal, handleOpenModal, handleCloseModal] = useContext(ModalForCountsContext)
+
+    function userFollowers() {
+        handleOpenModal({
+            arr: userData?.followers, name: `Подписчики ${userData?.login}`
+        })
+    }
+
+    function userFollowing() {
+        handleOpenModal({
+            arr: userData?.following, name: `Подписки ${userData?.login}`
+        })
     }
 
 
@@ -106,19 +159,21 @@ export default function BasicCard({ userData }) {
             alignItems: 'center'
         }}
         >
-            <Box>
-                <Avatar alt={userData?.login} src={urlAvatar} sx={{
-                    width: 150,
-                    height: 150
+            <Avatar
+                alt={userData?.login}
+                src={url + userData?.avatar?.url}
+                sx={{
+                    width: 130,
+                    height: 130
                 }}
-                />
-            </Box>
+            />
 
-            <Box sx={{
-                display: 'flex',
-                flexDirection: 'column',
-                marginLeft: '50px '
-            }}
+            <Box
+                sx={{
+                    display: 'flex',
+                    flexDirection: 'column',
+                    marginLeft: '50px '
+                }}
             >
                 <CardContent sx={{
                     flex: '1 0 auto'
@@ -138,7 +193,7 @@ export default function BasicCard({ userData }) {
                             {userData?.login}
                         </Typography>
 
-                        <UptadeProfileButton />
+                        <UptadeProfileButton data={userData} />
                     </Stack>
 
                     <Typography
@@ -183,6 +238,8 @@ export default function BasicCard({ userData }) {
                     </Typography>
                 </Stack>
             </Box>
+
+            {modalArray && <ModalWindow />}
         </Container>
     )
 }

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

@@ -1,489 +0,0 @@
-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 = (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 = (type, nameOfPromise, promise) =>
-  async dispatch => {
-    dispatch(actionPending(type, nameOfPromise)) //сигнализируем redux, что промис начался
-    try {
-      const payload = await promise //ожидаем промиса
-      dispatch(actionFulfilled(type, nameOfPromise, payload)) //сигнализируем redux, что промис успешно выполнен
-      return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
-    }
-    catch (error) {
-      dispatch(actionRejected(type, nameOfPromise, error)) //в случае ошибки - сигнализируем redux, что промис несложился
-    }
-  }
-
-
-
-
-// =============================================================
-// Запросы на бек
-
-// Запрос на регистрацию пользователя
-export const actionRegistration = (login, password) => actionPromise('PROMISE', 'Registration', gql(`mutation Registration($login:String!, $password:String!) {
-   createUser(login:$login, password:$password) {
-    _id login
-  }
-}`, {
-  login,
-  password
-})
-
-)
-
-
-
-// Запрос на логинизацию пользователя
-export const actionLogin = (login, password) => actionPromise('PROMISE', '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('PROMISE', '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('PROMISE', '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('PROMISE', 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('PROMISE', 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('PROMISE', '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('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{
-                url
-              }
-            }
-            comments {
-              _id
-            }
-    images{
-      url
-    }
-    	}
-    }`, {
-  // вот тут прописал 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 => {
-
-    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 => {
-
-    // запрашиваем информацию о пользователе
-    await dispatch(actionFindUserOne(_id, 'UserFindOne'))
-
-    // парсим счетчик, сколько постов у юзера
-    dispatch(actionPostsCount(_id, 'UserPostsCount'))
-
-    // запрашиваем список постов для юзера
-    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'))
-  }
-
-
-
-
-// ================= на удаление (начало)=====================
-
-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
-    }
-  }
-
-
-// ================= на удаление(конец) =====================
-
-
-
-// запрос на загрузку картинок на бек
-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('PROMISE', 'FilesUpload',
-  filesUpload(files)
-)
-
-
-
-
-
-
-
-// запрос на создание поста
-export const actionCreatePost = (params) => actionPromise('PROMISE', '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))
-    }
-  }
-
-
-
-
-

+ 44 - 233
js/Project/project/src/redux/action.js

@@ -147,22 +147,6 @@ export const actionFindPostOne = _id => actionPromise('PROMISE', 'PostFindOne',
 
 
 // запрос на поиск конкретного юзера(folowwing - это те, на кого я подписан. followers - те, кто на меня подписан)
-// export const actionFindUserOne = (_id, promiseName = 'UserFindOne') => actionPromise('PROMISE', 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('PROMISE', promiseName, gql(`query OneUserFind ($userOne: String) {
     UserFindOne(query: $userOne) {
       _id createdAt login nick 
@@ -201,25 +185,6 @@ export const actionfindPosts = () => actionPromise('PROMISE', 'PostsFind', gql(`
 
 
 // Запрос на поиск ленты постов для юзера
-// 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){
@@ -232,11 +197,13 @@ export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed',
               _id
             }
     images{
-      url
+      _id url
     }
     likes{
       _id owner{
-        _id login
+        _id login avatar{
+          _id url
+        }
       }
     }
     	}
@@ -248,25 +215,6 @@ export const actionFeedFindOne = (arr, sortOne, limitOne, promiseName = 'Feed',
 
 
 // Запрос ленты с пропуском определенного количества постов/комментов
-// 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){
@@ -468,182 +416,45 @@ export const actionUpdateProfile = params => actionPromise('PROMISE', 'UpdatePro
 
 
 
+// запрос поиска по юзерам
+export const actionSearchUser = params => actionPromise('SEARCH', 'Search', gql(`query UserSearch($findUser: String){
+  UserFind(query: $findUser){
+      _id login nick avatar{
+        _id url
+      }
+  }
+}`, {
+  findUser: JSON.stringify([{ params }])
+  // пример запроса: [{login:/владимир|voldde/}]
+}))
 
+// запрос поиска по постам
+export const actionSearchPost = params => actionPromise('SEARCH', 'Search', gql(`query PostSearch($findPost: String){
+  PostFind(query: $findPost){
+      _id title text owner{
+        _id login nick avatar{
+          _id url
+        }
+      }
+  }
+}`, {
+  findPost: params
+}))
 
-
-
-
-
-
-
-
-// =========================================
-// 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 => {
-
-//     // запрашиваем информацию о пользователе
-//     await dispatch(actionFindUserOne(_id, 'UserFindOne'))
-
-//     // парсим счетчик, сколько постов у юзера
-//     dispatch(actionPostsCount(_id, 'UserPostsCount'))
-
-//     // запрашиваем список постов для юзера
-//     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'))
-//   }
-
-
-
-
-// ================= на удаление (начало)=====================
-
-// 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
-//     }
-//   }
-
-
-// ================= на удаление(конец) =====================
-
-
-
-// запрос на загрузку картинок на бек
-// 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('PROMISE', 'FilesUpload',
-//   filesUpload(files)
-// )
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// санк для создания поста и последующего перехода на его страницу
-// export const actionFullCreatePost = (params) =>
-//   async dispatch => {
-//     const newPost = await dispatch(actionCreatePost(params))
-//     // console.log('Тут нужный резульата: ', newPost)
-
-//     if (newPost) {
-//       dispatch(actionFindPostOne(newPost._id))
-//     }
-//   }
-
-
-
-
-
+// запрос поиска по клмментам
+export const actionSearchComment = params => actionPromise('SEARCH', 'Search', gql(`query CommentSearch($findComment: String){
+  CommentFind(query: $findComment){
+      _id text
+    post{
+        _id
+      }
+    owner{
+        _id login nick
+      avatar{
+          _id url
+        }
+      }
+  }
+}`, {
+  findComment: params
+}))

+ 2 - 28
js/Project/project/src/redux/index.js

@@ -1,33 +1,7 @@
-import { createStore, combineRedusers, applyMiddleware } from 'redux';
+import { createStore, applyMiddleware } from 'redux';
 import thunk from "redux-thunk";
 
 import { totalReducer } from "./reducers";
 
-import { actionPending, actionFulfilled, actionRejected, actionPromise, actionFullLogin, actionLogin } from "./action";
-
-// объект со всеми редьюсерами
-// const reducers = {
-//     promise: localStoredReducer(promiseReducer, 'promise'),
-//     auth: localStoredReducer(authReducer, 'auth'),
-//     // cart: localStoredReducer(cartReducer, 'cart'),
-// }
-
-// const totalReducer = combineRedusers(reducers)
-
-// создаем store для редьюсера
-// export const store = createStore(promiseReducer, applyMiddleware(thunk))
-// store.subscribe(() => console.log(store.getState()))
-
-// 2 изменение
 export const store = createStore(totalReducer, applyMiddleware(thunk))
-store.subscribe(() => console.log(store.getState()))
-
-// запрос на логинизацию
-// store.dispatch(actionFullLogin('volddemar4ik', 'Qwerty1324'))
-
-
-
-// вот это раскомментитьь
-// if (localStorage.authToken == undefined) {
-//     store.dispatch(actionFullLogin('volddemar4ik', 'Qwerty1324'))
-// }
+store.subscribe(() => console.log(store.getState()))

+ 23 - 49
js/Project/project/src/redux/reducers.js

@@ -1,46 +1,5 @@
 import { combineReducers } from "redux"
 
-// feedReducer - а нужен ли он?
-// export function feedReducer(state = {}, { type, status, payload, error, nameOfPromise }) {
-//     if (type === 'PROMISE') {
-//         return {
-//             ...state,
-//             [nameOfPromise]: { status, payload, error }
-//         }
-//     }
-// }
-
-
-
-// promiseReducer
-export function promiseReducer(state = {}, { type, status, payload, error, nameOfPromise }) {
-    if (type === 'PROMISE') {
-        return {
-            ...state,
-            [nameOfPromise]: { status, payload, error }
-        }
-    }
-    return state
-}
-
-
-
-
-
-// feedReducer
-export function feedReducer(state = {}, { type, status, payload, error, nameOfPromise }) {
-    if (type === 'FEED') {
-        return {
-            ...state,
-            [nameOfPromise]: { status, payload, error }
-        }
-    }
-    return state
-}
-
-
-
-
 
 // раскодируем JWT-токен
 const jwtDecode = function (token) {
@@ -67,16 +26,15 @@ export function authReducer(state = {}, { type, token }) {
 
     if (type === 'AUTH_LOGOUT') {
         localStorage.clear()
-        return {}
 
+        return state = {}
     }
 
     return state
 }
 
 
-
-// localStoredReducer, который обрабатывает все наши редьюсеры для разных направлений
+// localStoredReducer, который обрабатывает все наши редьюсеры для разных направлений и добавляет в localStorage
 export function localStoredReducer(originalReducer, localStorageKey) {
     function wrapper(state, action) {
         if (!state) {
@@ -87,6 +45,7 @@ export function localStoredReducer(originalReducer, localStorageKey) {
 
             }
         }
+
         const newState = originalReducer(state, action)
         localStorage[localStorageKey] = JSON.stringify(newState)
 
@@ -96,13 +55,28 @@ export function localStoredReducer(originalReducer, localStorageKey) {
     return wrapper
 }
 
-// создаем объект с редьюсерами
+// универсальный создатель редьюсеров в зависимости от передаваемого типа
+function createReducer(type) {
+    function wrapper(state = {}, action) {
+        const { status, payload, error, nameOfPromise } = action
+        if (action.type === type) {
+            return {
+                ...state,
+                [nameOfPromise]: { status, payload, error }
+            }
+        }
+        return state
+    }
+    return wrapper
+}
+
+
 export const reducers = {
-    promise: localStoredReducer(promiseReducer, 'promise'),
     auth: localStoredReducer(authReducer, 'auth'),
-    feed: localStoredReducer(feedReducer, 'feed')
+    promise: localStoredReducer(createReducer('PROMISE'), 'promise'),
+    feed: localStoredReducer(createReducer('FEED'), 'feed'),
+    search: localStoredReducer(createReducer('SEARCH'), 'search')
 }
 
-// скармливаем объект с редьюсерами в combineReducers
-export const totalReducer = combineReducers(reducers)
+export const totalReducer = combineReducers(reducers);
 

+ 187 - 52
js/Project/project/src/redux/thunks.js

@@ -17,7 +17,10 @@ import {
     actionFindFollowing,
     actionAddLike,
     actionDeleteLike,
-    actionUpdateProfile
+    actionUpdateProfile,
+    actionSearchUser,
+    actionSearchPost,
+    actionSearchComment
 } from "./action"
 
 
@@ -58,68 +61,133 @@ export const actionFullRegistration = (login, password) =>
 // Разлогин и последующая полна очистка localStorage
 export const actionFullLogout = () =>
     async dispatch => {
-        await dispatch(actionAuthLogout())
-        return {}
+        const res = await dispatch(actionAuthLogout())
+
+        return res
     }
 
 
 
+
+
+
+
 // Запрос юзера (данные о пользователе + количество его постов + все его посты(100 шт))
-export const actionFullUserFindOne = _id =>
-    async dispatch => {
+// export const actionFullUserFindOne = _id =>
+//     async dispatch => {
 
-        // запрашиваем информацию о пользователе
-        await dispatch(actionFindUserOne(_id, 'UserFindOne'))
+//         // запрашиваем информацию о пользователе
+//         await dispatch(actionFindUserOne(_id, 'UserFindOne'))
 
-        // парсим счетчик, сколько постов у юзера
-        dispatch(actionPostsCount(_id, 'UserPostsCount'))
+//         // парсим счетчик, сколько постов у юзера
+//         dispatch(actionPostsCount(_id, 'UserPostsCount'))
 
-        // запрашиваем список постов для юзера
-        dispatch(actionFeedFindOne([_id], -1, 100, 'UserFeed'))
+//         // запрашиваем список постов для юзера
+//         dispatch(actionFeedFindOne([_id], -1, 100, 'UserFeed'))
+//     }
+export const actionFullUserFindOne = _id =>
+    dispatch => {
+        const requestsAboutUser = [
+            actionFindUserOne(_id, 'UserFindOne'),
+            actionPostsCount(_id, 'UserPostsCount'),
+            actionFeedFindOne([_id], -1, 100, 'UserFeed')
+        ]
+
+        return Promise.all(requestsAboutUser.map(item => dispatch(item)))
     }
 
 
 
+
+
+
 // Запрос AboutMe для главной
+// export const actionAboutMe = () =>
+//     async (dispatch, getState) => {
+//         const myId = getState()?.auth?.payload?.sub?.id
+
+//         // диспатчим запрос AboutMe (о себе)
+//         const myData = await dispatch(actionFindUserOne(myId, 'AboutMe'))
+
+//         // собираем список id моих подписок
+//         let followingList = []
+//         if (myData?.following) {
+//             followingList = (myData.following).map(user => user?._id)
+//         }
+
+//         // собираем id моих подписок и подписчиков
+//         const podpisotaList = []
+//         for (const key in myData) {
+//             if (key === 'following' || key === 'followers') {
+//                 for (const item of myData[key]) {
+//                     if (!podpisotaList.includes(item._id)) {
+//                         podpisotaList.push(item._id)
+//                     }
+//                 }
+//             }
+//         }
+
+//         // шлем все запросы на меня одновременно
+//         const requestsAboutMe = [
+//             actionPostsCount(myId, 'MyPostsCount'),
+//             actionFeedFindOne(followingList, -1, 10, 'MyFeed'),
+//             actionFindFollowers(podpisotaList),
+//             actionFindFollowing(podpisotaList)
+//         ]
+
+//         Promise.all(requestsAboutMe.map(item => dispatch(item)))
+
+//         // парсим счетчик, сколько постов у меня
+//         // dispatch(actionPostsCount(myId, 'MyPostsCount'))
+//         // диспатчим ленту моих постов (первый параметр - список id, второй - это сортировка постов от новых)
+//         // dispatch(actionFeedFindOne(followingList, -1, 10, 'MyFeed'))
+//         // диспатчим список для рекомендаций
+//         // await Promise.all([dispatch(actionFindFollowers(podpisotaList)), dispatch(actionFindFollowing(podpisotaList))])
+//     }
 export const actionAboutMe = () =>
     async (dispatch, getState) => {
         const myId = getState()?.auth?.payload?.sub?.id
 
         // диспатчим запрос AboutMe (о себе)
-        const userData = await dispatch(actionFindUserOne(myId, 'AboutMe'))
+        await dispatch(actionFindUserOne(myId, 'AboutMe'))
+            .then(myData => {
+                // собираем список id моих подписок
+                let followingList = []
+                if (myData?.following) {
+                    followingList = myData.following.map(user => user?._id)
+                }
+
+                // собираем id моих подписок и подписчиков
+                const podpisotaList = []
+                for (const key in myData) {
+                    if (key === 'following' || key === 'followers') {
+                        for (const item of myData[key]) {
+                            if (!podpisotaList.includes(item._id)) {
+                                podpisotaList.push(item._id)
+                            }
+                        }
+                    }
+                }
+
+                // шлем все запросы на меня одновременно, только после выполнения dispatch(actionFindUserOne(myId, 'AboutMe'))
+                const requestsAboutMe = [
+                    actionPostsCount(myId, 'MyPostsCount'),
+                    actionFeedFindOne(followingList, -1, 10, 'MyFeed'),
+                    actionFindFollowers(podpisotaList),
+                    actionFindFollowing(podpisotaList)
+                ]
+
+                return Promise.all(requestsAboutMe.map(item => dispatch(item)))
+            })
+    }
+
 
-        // парсим счетчик, сколько постов у меня
-        dispatch(actionPostsCount(myId, 'MyPostsCount'))
 
-        let followingList = []
-        // проверяем, есть ли вообще подписчики и если да, парсим их
-        if (userData?.following) {
-            // получаем id всех подписок
-            followingList = (userData.following).map(user => user?._id)
-        }
 
-        // запрашиваем формирование ленты моих постов (первый параметр - список id, второй - это сортировка постов от новых)
-        dispatch(actionFeedFindOne(followingList, -1, 10, 'MyFeed'))
 
 
-        // =====================================
-        // собираем id подписок и подписчиков
-        // console.log(33, userData)
-        const podpisotaList = []
 
-        for (const key in userData) {
-            if (key === 'following' || key === 'followers') {
-                for (const item of userData[key]) {
-                    if (!podpisotaList.includes(item._id)) {
-                        podpisotaList.push(item._id);
-                    }
-                }
-            }
-        }
 
-        // диспатчим список для рекомендаций
-        await Promise.all([dispatch(actionFindFollowers(podpisotaList)), dispatch(actionFindFollowing(podpisotaList))])
-    }
 
 
 
@@ -141,6 +209,10 @@ export const actionDownloadFeed = (skip) =>
 
 
 
+
+
+
+
 // запрос на загрузку картинок на бек
 function fileUpload(file) {
     const formData = new FormData()
@@ -167,7 +239,10 @@ export const actionFilesUpload = files => actionPromise('PROMISE', 'FilesUpload'
 
 
 
-// Создание поста и последующий переход на него
+
+
+
+// запрос на создание поста и последующий переход на него
 export const actionFullCreatePost = (params) =>
     async dispatch => {
         const newPost = await dispatch(actionCreatePost(params))
@@ -186,14 +261,11 @@ export const actionFullFindCommentsPostOne = id =>
     async dispatch => {
         const comments = await dispatch(actionFindCommentsPostOne(id))
 
-        // console.log('comments: ', comments)
+        return comments
+    }
 
-        // const res = commentsTreeConstructor(comments)
 
-        // console.log('result: ', res)
 
-        // return res
-    }
 
 
 
@@ -202,8 +274,6 @@ export const actionFullAddComment = (nameOfPromise, params, id) =>
     async dispatch => {
         const newComment = await dispatch(actionAddComment(nameOfPromise, params))
 
-
-        console.log('comment_id: ', id)
         if (typeof newComment._id === 'string') {
             dispatch(actionFullFindCommentsPostOne(id))
         }
@@ -221,9 +291,10 @@ export const actionFullAddLike = params =>
         // обновляем данные поста с бека, если лайк был для поста
         if (res?.post) {
             dispatch(actionFindPostOne(res?.post?._id))
+            dispatch(actionAboutMe())
         }
 
-
+        // обновляем данные с бека, если лайк был для комментария
         if (res?.comment) {
             dispatch(actionFullFindCommentsPostOne(res?.comment?.post?._id))
         }
@@ -233,17 +304,18 @@ export const actionFullAddLike = params =>
 
 
 
-// удаление лайка с последующим удаление к определенной сущности и последующим обновление определенной сущности
+// удаление лайка с последующим обновлением определенной сущности
 export const actionFullDeleteLike = params =>
     async dispatch => {
         const res = await dispatch(actionDeleteLike(params))
 
-
-        // обновляем данные комментариев к посту с бека, если лайк был для поста
+        // обновляем данные поста с бека, если лайк был для поста
         if (res?.post) {
             dispatch(actionFindPostOne(res.post._id))
+            dispatch(actionAboutMe())
         }
 
+        // обновляем данные с бека, если лайк был для комментария
         if (res?.comment) {
             dispatch(actionFullFindCommentsPostOne(res?.comment?.post?._id))
         }
@@ -253,13 +325,76 @@ export const actionFullDeleteLike = params =>
 
 
 
-// запрос на обновление профиля и последующая отправка запрос обо мне на бек
-export const actionFullUpdateProfile = params =>
+// запрос на обновление моего профиля и последующая отправка запроса обо мне на бек
+// export const actionFullUpdateProfile = params =>
+export const actionFullUpdateProfile = (params, id) =>
     async dispatch => {
         const res = await dispatch(actionUpdateProfile(params))
 
+        if (res._id) {
+            dispatch(actionAboutMe())
+        }
+
+        // нужно, чтоб обновлялась инфа на моем профиле, если он открыт как обычный юзер
+        if (res._id == id) {
+            dispatch(actionFullUserFindOne(id))
+        }
+    }
+
+
+// запрос на подписку/отписку на странице пользователя
+export const actionUserPageSubscribing = (params, id) =>
+    async dispatch => {
+        const res = await dispatch(actionUpdateProfile(params))
 
         if (res._id) {
             dispatch(actionAboutMe())
+            dispatch(actionFullUserFindOne(id))
+        }
+    }
+
+
+
+// запрос на поиск элементов
+export const actionFullSEarch = param =>
+    async dispatch => {
+
+        // создаем список сущностей, которые нам нужны
+        const variables = {
+            user: ['login', 'nick'],
+            post: ['title', 'text'],
+            comment: ['text']
+        }
+
+
+        // массив для акшонов
+        let searchPromises = []
+
+        // перебираем variables
+        for (const [key, variable] of Object.entries(variables)) {
+            if (key.toLowerCase() === 'user') {
+                for (const item of variable) {
+                    searchPromises.push(actionSearchUser([{ item: param }]))
+                }
+            }
+
+            if (key.toLowerCase() === 'post') {
+                for (const item of variable) {
+                    searchPromises.push(actionSearchPost([{ item: param }]))
+                }
+            }
+
+            if (key.toLowerCase() === 'comment') {
+                for (const item of variable) {
+                    searchPromises.push(actionSearchComment([{ item: param }]))
+                }
+            }
         }
+
+        console.log('searchPromises: ', searchPromises)
+
+        // запускаем все акшоны
+        Promise.all(searchPromises.map(item => dispatch(item)))
+
+
     }