Просмотр исходного кода

Add SearchUsers + start PostsFeed

LenDoc 2 лет назад
Родитель
Сommit
12f12fbecc
7 измененных файлов с 639 добавлено и 400 удалено
  1. 98 387
      src/App.js
  2. 3 0
      src/App.scss
  3. 127 13
      src/actions/index.js
  4. 55 0
      src/components/DropZone.js
  5. 138 0
      src/components/NewPost.js
  6. 138 0
      src/components/Post.js
  7. 80 0
      src/components/User.js

+ 98 - 387
src/App.js

@@ -4,31 +4,28 @@ import { Router, Route, Link, Redirect, Switch } from 'react-router-dom'
 import createHistory from 'history/createBrowserHistory'
 import React, { useMemo, useState, useEffect } from 'react'
 import { store } from './reducers'
+import { Basic } from './components/DropZone'
+import {CPageAboutUser} from './components/User'
+import { PageCreatePost,AddPost } from './components/NewPost'
+import {CPost } from './components/Post'
+
 import { sortableContainer, sortableElement } from 'react-sortable-hoc'
 import { arrayMove, arrayMoveImmutable, arrayMoveMutable } from 'array-move'
-import { useDropzone } from 'react-dropzone'
 import 'antd/dist/antd.css'
+
 import {
-  actionPromise,
-  actionPending,
-  actionFulfilled,
-  actionFullLogin,
-  actionRegister,
-  actionFullRegister,
-  gql,
+  backendURL,
   actionAboutMe,
-  actionAvatar,
-  actionUploadFile,
-  actionUploadFiles,
   actionSetAvatar,
-  actionPostUpsert,
-  backendURL,
-  actionAllPosts,
-  actionOnePost,
+  actionPostsFeed,
+  actionAllFollowing,
+  actionAllFollowers,
+  actionPostsMyFollowing2,
+  actionSearchUser
 } from './actions'
 import { Upload, Button, DatePicker, Space } from 'antd'
 import moment from 'moment'
-import { UploadOutlined } from '@ant-design/icons'
+import { UploadOutlined,SearchOutlined } from '@ant-design/icons'
 import ImgCrop from 'antd-img-crop'
 import { Avatar, Image, Divider, Radio } from 'antd'
 import { UserOutlined } from '@ant-design/icons'
@@ -36,425 +33,139 @@ import user from './materials/user.png'
 import photoNotFound from './materials/photoNotFound.png'
 // import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
 // import { Carousel } from 'react-responsive-carousel';
-import { Carousel } from 'antd'
+import { Carousel,Popover } from 'antd'
 import { LeftCircleFilled, RightCircleFilled } from '@ant-design/icons'
+import { Input,Select } from 'antd';
 console.log(store.getState())
 store.subscribe(() => console.log(store.getState()))
-const PageMain = () => <div className="PageMain">ГЛАВНАЯ</div>
-
-const Card = ({ post, onPost }) => (
-  <>
-    <Link to={`/post/${post?._id}`} onClick={() => onPost(post?._id)}>
-      {post?.images && post?.images[0] && post.images[0]?.url ? (
-        <img
-          className="Card"
-          src={backendURL + '/' + post.images[0].url}
-          style={{ maxWidth: '200px', maxHeight: '200px' }}
-        />
-      ) : (
-        <img
-          className="Card"
-          src={photoNotFound}
-          style={{ maxWidth: '200px', maxHeight: '200px' }}
-        />
-      )}
-      {/* {console.log(post?._id)} */}
-    </Link>
-  </>
-)
-const SampleNextArrow = (props) => {
-  const { className, style, onClick } = props
-  return (
-    <div
-      className="carousel-control-next"
-      style={{
-        fontSize: '50px',
-        color: '#a8a8a8',
-        position: 'absolute',
-        left: '100%',
-        top: '50%',
-        margin: 'auto',
-      }}
-      onClick={onClick}
-    >
-      <RightCircleFilled />
-    </div>
-  )
-}
-
-const SamplePrevArrow = (props) => {
-  const { className, style, onClick } = props
-  return (
-    <div
-      className="carousel-control-prev"
-      style={{
-        color: '#a8a8a8',
-        fontSize: '50px',
-        position: 'absolute',
-        margin: 'auto',
-        right: '100%',
-        top: '50%'
-      }}
-      onClick={onClick}
-    >
-      <LeftCircleFilled />
-    </div>
-  )
-}
 
-const MyCarousel = ({ images = [] }) => {
-  console.log('IMAGES', images)
-  return (
-    <>
-      <div>
-        <Carousel
-          style={{
-            display: 'block',
-            minWidth: '500px',
-            minHeight: '500px',
-            background: 'blue',
-          }}
-          effect="fade"
-          arrows
-          nextArrow={<SampleNextArrow />}
-          prevArrow={<SamplePrevArrow />}
-        >
-          {images &&
-            images.map((i, index) =>
-              i?.url ? (
-                <div key={index}>
-                  <img
-                    className="PostImage"
-                    src={backendURL + '/' + i?.url}
-                    style={{
-                      display: 'flex',
-                      alignItems: 'center',
-                      maxWidth: '400px',
-                      maxHeight: '400px',
-                    }}
-                  />
-                </div>
-              ) : (
-                <div>
-                  <img
-                    className="PostImage"
-                    src={photoNotFound}
-                    style={{ maxWidth: '400px', maxHeight: '400px' }}
-                  />
-                </div>
-              ),
-            )}
-        </Carousel>
-      </div>
-    </>
-  )
-}
-const PagePost = ({ onePost, aboutMe: { avatar, login } = {}, onPost }) => {
-  return (
-    <>
-      <MyCarousel images={onePost?.images} />
+console.log('ABOUT ME',store.getState().auth?.payload?.sub?._id);
+const PageMain = () => <div className="PageMain">ГЛАВНАЯ</div>
 
-      {avatar ? (
-        <Avatar
-          style={{ width: '50px', height: '50px' }}
-          src={backendURL + '/' + avatar?.url}
-        />
-      ) : (
-        <Avatar style={{ width: '50px', height: '50px' }} src={user} />
-      )}
-      <h2> {onePost?.title || ''} </h2>
-      <h2> {onePost?.text || ''} </h2>
-    </>
-  )
-}
 
-const CPost = connect((state) => ({
-  onePost: state.promise.onePost?.payload,
-  aboutMe: state.promise?.aboutMe?.payload,
-}))(PagePost)
-const PageAboutMe = ({
-  aboutMe: { _id, login, nick, createdAt, avatar, followers, following } = {},
-  allPosts,
-  onPosts,
-  onPost,
-}) => {
+const PageFeed = ({ aboutMe,allFollowing, onPostsFeed, onAllFollowing}) => {
+// const Following =[]
   useEffect(() => {
-    onPosts()
+    onAllFollowing(aboutMe?._id)
   }, [])
-  // console.log('CREATED AT',new Intl.DateTimeFormat().format(createdAt));
-  return (
-    <section className="AboutMe">
-      <Avatar
-        style={{ width: '150px', height: '150px', position: 'absolute' }}
-        src={backendURL + '/' + avatar?.url || user}
-      />
-      <div className="Info">
-        <h1> {login}</h1>
-        <h3>
-          {' '}
-          Created Account: {new Intl.DateTimeFormat('en-GB').format(createdAt)}
-        </h3>
-        <div style={{ display: 'flex' }}>
-          {/* {allPosts?.length} style={{display: 'flex',justifyContent: 'space-between'}}*/}
-          <h3> {allPosts?.length} posts </h3>
-
-          <h3 style={{ marginLeft: '20px' }}>
-            {' '}
-            {followers?.length} followers{' '}
-          </h3>
+  useEffect(() => {
+     onPostsFeed(allFollowing?.following?.map(({_id})=>(_id)))
+  }, [])
+  console.log('allFollowing',allFollowing?.following?.map(({_id})=>(_id)))
 
-          <h3 style={{ marginLeft: '20px' }}>
-            {' '}
-            {following?.length} following{' '}
-          </h3>
-        </div>
-        <h3> nick: {nick == null ? login : nick}</h3>
-        <div
-          style={{
-            display: 'flex',
-            flexWrap: 'wrap',
-            padding: '20px',
-            margin: '20px',
-          }}
-        >
-          {(allPosts || [])?.map((item) => (
-            <Card post={item} onPost={onPost} />
-          ))}
-        </div>
-        {/* <h3> Created Account: {
-<div>
-<img
-      src={backendURL + '/' + allPosts?.url}
-      style={{ maxWidth: '200px', maxHeight: '200px' }}/>
-</div>
-date} </h3> */}
-      </div>
-    </section>
-  )
-}
-const CPageAboutMe = connect(
-  (state) => ({
-    aboutMe: state.promise?.aboutMe?.payload,
-    allPosts: state.promise?.allPosts?.payload,
-  }),
-  { onPosts: actionAllPosts, onPost: actionOnePost },
-)(PageAboutMe)
-const PageCreatePost = () => (
-  <div style={{ maxWidth: '700px', maxHeight: '700px', background: '#FFFACD' }}>
-    <h2>Edit Post</h2>
-    <CBasic />
+  return <>
+ <div style={{background: '#FFFACD' }}>
+    <h2>Feed</h2>
+  
   </div>
-)
+   </>
+}
+const CPageFeed =connect((state)=>({
+  aboutMe: state.promise.aboutMe?.payload , 
+   allFollowing: state.promise?.allFollowing?.payload,
+   allPosts: state.promise?.allPosts?.payload,
+   postsFeed: state.promise?.postsFeed?.payload,
+   followingPosts: state.promise?.followingPosts?.payload
+  }), {onPostsFeed:actionPostsFeed,
+      onAllFollowing:actionAllFollowing})(PageFeed)
 const Main = () => (
   <main>
     <Switch>
       <Route path="/" exact component={PageMain} />
-      <Route path="/profile/:_id" component={CPageAboutMe} />
+      <Route path="/profile/:_id" component={CPageAboutUser} />
       <Route path="/edit/post" component={PageCreatePost} />
       <Route path="/post/:_id" component={CPost} />
+      <Route path="/feed" component={CPageFeed} />
+
       {/* <CBasic /> */}
     </Switch>
   </main>
 )
-const Header = () => (
-  <section className="Header">
+
+const ResultUserFind =({userFind})=>
+<div>
+{userFind?.map(({_id, login, avatar})=>(
+  <Link to={`/profile/${_id}`} >
+    <Avatar
+          style={{ width: '20px', height: '20px',marginRight:'30px', position: 'absolute' }}
+          src={backendURL + '/' + avatar?.url || user}
+        />
+       
+          <h3 style={{ marginLeft:'30px'}} > {login}</h3>
+  </Link>
+))}
+</div>
+
+const SearchUser =({onSearch,searchUser})=>{
+  // const [value, setValue]=useState('')
+  const onSearchUser = value => onSearch(value)
+   const { Search } = Input;
+return <>
+    <Popover  placement="bottom" content={<ResultUserFind userFind={searchUser}/>} trigger="click">
+    <Search
+      placeholder="input search text"
+      allowClear
+      enterButton="Search"
+      size="large"
+      onSearch={onSearchUser}
+    />
+</Popover>
+</>
+}
+const CSearch = connect((state)=>({searchUser:state.promise?.searchUser?.payload}), {onSearch:actionSearchUser})(SearchUser)
+const Header = () => {
+
+ return  <section className="Header">
+    <CSearch/>
+    {/* <Button icon={<SearchOutlined />}>Search</Button> */}
+    <CFeed/>
     <AddPost />
     <Recommendations />
     <Likes />
     <CUser />
   </section>
-)
-const Likes = () => <button className="Likes"> Likes </button>
+}
+const Likes = () => <Button className="Likes"> Likes </Button>
+const Feed = ({aboutMe,onAllFollowing}) => 
+<>
+
+<Link className="Feed" to={`/feed`}>
+
+<Button className="Feed" onClick={()=>onAllFollowing(aboutMe?._id)}> Feed </Button>
+</Link>
+</>
+const CFeed =connect((state) => ({aboutMe: state.promise?.aboutMe?.payload}),{onAllFollowing:actionAllFollowing})(Feed)
 const Recommendations = () => (
-  <button className="Recomendations"> Recommendations </button>
+  <Button className="Recomendations"> Recommendations </Button>
 )
-const AddPost = ({ children }) => {
-  const [state, setState] = useState(false)
-
-  return (
-    <>
-      <Link to={`/edit/post`}>
-        <button onClick={() => setState(!state)}> + </button>
-        {!state && children}
-      </Link>
-    </>
-  )
-}
 
 const CBasic = connect(null, { onLoad: actionSetAvatar })(Basic)
 // const CAddPost =connect(null,{actionPostUpsert})
 const User = ({ aboutMe: { _id, login, avatar } = {} }) => (
+
   <Link className="User" to={`/profile/${_id}`}>
     <Avatar src={backendURL + '/' + avatar?.url || user} />
   </Link>
 )
 
 const CUser = connect((state) => ({ aboutMe: state.promise.aboutMe?.payload }))(
-  User,
-)
-
-function Basic({ onLoad }) {
-  const { acceptedFiles, getRootProps, getInputProps } = useDropzone()
-  const files = acceptedFiles.map((file) => (
-    <li key={file.path}>
-      {file.path} - {file.size} bytes
-    </li>
-  ))
-  useEffect(() => {
-    acceptedFiles[0] && onLoad(acceptedFiles[0])
-  }, [acceptedFiles])
-  return (
-    <section className="container">
-      <div {...getRootProps({ className: 'Dropzone' })}>
-        <input {...getInputProps()} />
-        <Button icon={<UploadOutlined />}>
-          Drag 'n' drop some files here, or click to select files
-        </Button>
-      </div>
-      <aside>
-        <h4 style={{ color: 'black' }}>Files</h4>
-        <ul>{files}</ul>
-      </aside>
-    </section>
-  )
-}
-const defaultPost = {
-  _id: '620cfd26ad55d22f3e2fb336',
-  title: 'Bmw',
-  text: 'Bmw',
-  images: [
-    {
-      _id: '620b8374ad55d22f3e2fb316',
-      url: 'images/e125a428191726307968880977dac103',
-    },
-    {
-      _id: '620b8399ad55d22f3e2fb317',
-      url: 'images/4ae46578989c497582995ba8caeb5de5',
-    },
-    {
-      _id: '620b83b0ad55d22f3e2fb318',
-      url: 'images/ae839539f61249b15feda98cad7eb858',
-    },
-  ],
-}
-
-store.getState().auth?.token && store.dispatch(actionAboutMe())
-
-const SortableItem = sortableElement(({ url }) => (
-  <li>
-    <img
-      src={backendURL + '/' + url}
-      style={{ maxWidth: '200px', maxHeight: '200px' }}
-    />
-  </li>
-))
-
-const SortableContainer = sortableContainer(({ children }) => {
-  return (
-    <>
-      <ul>{children}</ul>
-
-      {/* <input value={title}/> */}
-    </>
-  )
-})
-const Input = ({ state, onChangeText }) => (
-  <input
-    className="Input"
-    value={state}
-    placeholder={state || ''}
-    onChange={onChangeText}
-  />
+  User
 )
-const PostEditor = ({ post = defaultPost, onSave, onFileDrop, fileStatus }) => {
-  //по файлу в дропзоне:
-  //дергать onFileDrop
-  //fileStatus - информация о заливке файла из redux
-  //через useEffect дождаться когда файл зальется
-  console.log('STATUS ', fileStatus?.status)
-  console.log('ON FILE DROP ', onFileDrop)
-
-  const [state, setState] = useState(post)
-  useEffect(() => {
-    fileStatus?.status == 'FULFILLED' &&
-      setState({
-        ...state,
-        images: [
-          ...state.images,
-          {
-            _id: fileStatus?.payload?._id,
-            url: fileStatus?.payload?.url,
-          },
-        ],
-      })
-  }, [fileStatus])
-
-  const onSortEnd = ({ oldIndex, newIndex }) => {
-    setState({
-      ...state,
-      images: arrayMoveImmutable(state.images, oldIndex, newIndex),
-    })
-  }
-  const onChangeTitle = (event) =>
-    setState({
-      ...state,
-      title: event.target.value,
-    })
-
-  const onChangeText = (event) =>
-    setState({
-      ...state,
-      text: event.target.value,
-    })
-  const onRemoveImage = (_id) =>
-    setState({
-      ...state,
-      images: state.images.filter((item) => item._id !== _id),
-    })
-  return (
-    <section className="Post">
-      <Basic onLoad={onFileDrop} />
-      <SortableContainer onSortEnd={onSortEnd}>
-        {(state.images || []).map(({ _id, url }, index) => (
-          <>
-            <SortableItem key={`item-${_id}`} index={index} url={url} />
-            <button onClick={() => onRemoveImage(_id)}> x </button>
-          </>
-        ))}
-      </SortableContainer>
-      <h1 className="Title"> Title </h1>
-      <Input state={state.title || ''} onChangeText={onChangeTitle} />
-      <h1 className="Title"> Text </h1>
-
-      <Input state={state.text || ''} onChangeText={onChangeText} />
-      <button
-        disabled={state?.images?.length == 0}
-        onClick={() => onSave(state)}
-      >
-        Save
-      </button>
-    </section>
-  )
-}
-const CPostEditor = connect(
-  (state) => ({ fileStatus: state.promise?.uploadFiles }),
-  {
-    onSave: actionPostUpsert,
-    onFileDrop: actionUploadFiles,
-  },
-)(PostEditor)
-// fileStatus=connect((state)=>(state.promise?.uploadFile))(CUploadFile)
 
 const history = createHistory()
 function App() {
+  if (store.getState().auth?.token &&store.getState().auth?.payload?.sub?._id)
+ store.dispatch(actionAboutMe(store.getState().auth?.payload?.sub?._id))
+ 
   return (
     <Router history={history}>
       <Provider store={store}>
+        
         <div className="App">
           <Header />
           <Divider />
           <Main />
-          {/* <PostEditor /> */}
+          {/* <CPostEditor /> */}
           {/* <CPost /> */}
           {/* <Gallery/> */}
         </div>

+ 3 - 0
src/App.scss

@@ -33,6 +33,9 @@
 .User{
   padding: 10px;
 }
+.Feed{
+  margin-right: 10px;
+}
 .Post{
   max-width: 600px;
   display: block; margin: 0 auto;

+ 127 - 13
src/actions/index.js

@@ -100,10 +100,12 @@ export const uploadFile = (file) => {
 }
 export const actionUploadFile = (file) =>
   actionPromise('uploadFile', uploadFile(file))
-//тут еще неправильно
-export const actionUploadFiles = (files) =>
-  actionPromise('uploadFiles', Promise.all([uploadFile(files)]))
 
+export const actionUploadFiles = (files) =>
+  actionPromise(
+    'uploadFiles',
+    Promise.all(files?.map((file) => uploadFile(file))),
+  )
 const actionAvatar = (imageId) => async (dispatch, getState) => {
   await dispatch(
     actionPromise(
@@ -121,7 +123,7 @@ const actionAvatar = (imageId) => async (dispatch, getState) => {
     ),
   )
 }
-export const actionAboutMe = () => async (dispatch, getState) => {
+export const actionAboutMe = (id) => async (dispatch, getState) => {
   await dispatch(
     actionPromise(
       'aboutMe',
@@ -134,12 +136,32 @@ export const actionAboutMe = () => async (dispatch, getState) => {
   }
 }`,
         {
-          userId: JSON.stringify([{ _id: getState().auth?.payload?.sub?.id }]),
+          userId: JSON.stringify([{ _id:id }]),
+        },
+      ),
+    ),
+  )
+}
+export const actionAboutUser = (_id) => async (dispatch, getState) => {
+  await dispatch(
+    actionPromise(
+      'aboutUser',
+      gql(
+        `query AboutUser($userId:String){
+  UserFindOne(query:$userId)
+    {
+      _id createdAt login nick avatar{_id url} 
+      followers{_id login nick} following{_id login nick}
+    }
+}`,
+        {
+          userId: JSON.stringify([{ _id: _id }]),
         },
       ),
     ),
   )
 }
+
 export const actionPostUpsert = (post) =>
   actionPromise(
     'postUpsert',
@@ -172,6 +194,8 @@ export const actionAllPosts = () => async (dispatch, getState) => {
         {
           userId: JSON.stringify([
             { ___owner: getState().auth?.payload?.sub?.id },
+
+            { sort: [{ _id: -1 }] },
           ]),
         },
       ),
@@ -180,20 +204,60 @@ export const actionAllPosts = () => async (dispatch, getState) => {
 }
 export const actionOnePost = (_id) => async (dispatch) => {
   await dispatch(
-  actionPromise(
-    'onePost',
-    gql(
-      `query OneFind($post:String){
+    actionPromise(
+      'onePost',
+      gql(
+        `query OneFind($post:String){
   PostFindOne(query:$post){
            _id title text images{_id url}
     }
 }`,
-      {
-        post: JSON.stringify([{ _id }]),
-      },
+        {
+          post: JSON.stringify([{ _id }]),
+        },
+      ),
     ),
-  ))
+  )
+}
+export const actionAllFollowers = (_id) => async (dispatch) => {
+  await dispatch(
+    actionPromise(
+      'allFollowers',
+      gql(
+        `query AllFollowers($userId:String){
+  UserFindOne(query:$userId)
+    {
+          followers{_id login}
+    }
+}`,
+        {
+          userId: JSON.stringify([{ _id }]),
+        },
+      ),
+    ),
+  )
+}
+
+export const actionAllFollowing = (_id) => async (dispatch) => {
+  await dispatch(
+    actionPromise(
+      'allFollowing',
+      gql(
+        `query AllFollowing($userId:String){
+  UserFindOne(query:$userId)
+    {
+          following{_id login}
     }
+}`,
+        {
+          userId: JSON.stringify([{ _id }]),
+        },
+      ),
+    ),
+  )
+}
+
+// query:"[{\"_id\": \"62068eaaad55d22f3e2fb250\"}]")
 export const actionSetAvatar = (file) => async (dispatch) => {
   let result = await dispatch(actionUploadFile(file))
   if (result) {
@@ -201,3 +265,53 @@ export const actionSetAvatar = (file) => async (dispatch) => {
     await dispatch(actionAboutMe())
   }
 }
+
+export const actionPostsFeed = (Following) => async (dispatch) => {
+  await dispatch(
+    actionPromise(
+      'postsFeed',
+      gql(`query PostsFeed($ownerId:String){
+	          PostFind(query:$ownerId){
+            owner{_id login avatar{url}}
+            images{_id url} title text
+            _id likesCount 
+            likes{
+                owner{				
+                    login avatar {url}
+                  }
+            }
+      	}
+    }`,
+        {
+          ownerId: JSON.stringify([
+            { ___owner: {$in: Following}},
+            {
+              sort: [{ _id: -1 }], //сортировка в обратном хронологическом порядке
+              skip: [0], //отступаем 1000 записей
+               limit: [10], //100 записей максимум
+            },
+          ]),
+        },
+      ),
+    ),
+  )
+}
+
+export const actionSearchUser =(userName)=> async (dispatch)=>{
+
+  await dispatch(actionPromise(
+    'searchUser', gql(`
+    query gf($query: String){
+        UserFind(query: $query){
+            _id, login avatar{url}
+        }
+    }`, {query: JSON.stringify([
+                {
+                    $or: [{login: `/${userName}/`}] //регулярки пишутся в строках
+                },
+                {
+                    sort: [{login: 1}]} //сортируем по title алфавитно
+                ])
+    })
+  ))
+}

+ 55 - 0
src/components/DropZone.js

@@ -0,0 +1,55 @@
+import { useDropzone } from 'react-dropzone'
+import React, { useMemo, useState, useEffect } from 'react'
+import { Upload, Button, DatePicker, Space } from 'antd'
+import { UploadOutlined,SearchOutlined } from '@ant-design/icons'
+import { sortableContainer, sortableElement } from 'react-sortable-hoc'
+import {backendURL} from '../actions'
+import { arrayMove, arrayMoveImmutable, arrayMoveMutable } from 'array-move'
+
+export function Basic({ onLoad }) {
+    const { acceptedFiles, getRootProps, getInputProps } = useDropzone()
+    const files = acceptedFiles.map((file) => (
+      <li key={file.path}>
+        {file.path} - {file.size} bytes
+      </li>
+    ))
+    console.log('acceptedFiles',acceptedFiles)
+    useEffect(() => {
+      if(acceptedFiles[0])
+        onLoad(acceptedFiles[0])
+    }
+    , [acceptedFiles])
+    return (
+      <section className="container">
+        <div {...getRootProps({ className: 'Dropzone' })}>
+          <input {...getInputProps()} />
+          <Button icon={<UploadOutlined />}>
+            Drag 'n' drop some files here, or click to select files
+          </Button>
+        </div>
+        <aside>
+          <h4 style={{ color: 'black' }}>Files</h4>
+          <ul>{files}</ul>
+        </aside>
+      </section>
+    )
+  }
+  
+export const SortableItem = sortableElement(({ url }) => (
+    <li>
+      <img
+        src={backendURL + '/' + url}
+        style={{ maxWidth: '200px', maxHeight: '200px' }}
+      />
+    </li>
+  ))
+  
+ export  const SortableContainer = sortableContainer(({ children }) => {
+    return (
+      <>
+        <ul>{children}</ul>
+  
+        {/* <input value={title}/> */}
+      </>
+    )
+  })

+ 138 - 0
src/components/NewPost.js

@@ -0,0 +1,138 @@
+import React, { useMemo, useState, useEffect } from 'react'
+import { Router, Route, Link, Redirect, Switch } from 'react-router-dom'
+import { Provider, connect } from 'react-redux'
+import { actionUploadFile, actionUploadFiles, actionPostUpsert} from '../actions'
+import { Upload, Button, DatePicker, Space } from 'antd'
+import {Basic, SortableContainer, SortableItem } from '../components/DropZone'
+import { arrayMove, arrayMoveImmutable, arrayMoveMutable } from 'array-move'
+
+const defaultPost = {
+  _id: '620cfd26ad55d22f3e2fb336',
+  title: 'Bmw',
+  text: 'Bmw',
+  images: [
+    {
+      _id: '620b8374ad55d22f3e2fb316',
+      url: 'images/e125a428191726307968880977dac103',
+    },
+    {
+      _id: '620b8399ad55d22f3e2fb317',
+      url: 'images/4ae46578989c497582995ba8caeb5de5',
+    },
+    {
+      _id: '620b83b0ad55d22f3e2fb318',
+      url: 'images/ae839539f61249b15feda98cad7eb858',
+    },
+  ],
+}
+
+export const PageCreatePost = () => (
+    <div style={{background: '#FFFACD' }}>
+      <h2>Edit Post</h2>
+      <CPostEditor />
+    </div>
+  )
+  
+export const AddPost = ({ children }) => {
+    const [state, setState] = useState(false)
+  
+    return (
+      <>
+        <Link to={`/edit/post`}>
+          <Button onClick={() => setState(!state)}> + </Button>
+          {!state && children}
+        </Link>
+      </>
+    )
+  }
+
+  const Input = ({ state, onChangeText }) => (
+    <input
+      className="Input"
+      value={state}
+      placeholder={state || ''}
+      onChange={onChangeText}
+    />
+  )
+const PostEditor = ({ post = defaultPost, onSave, onFileDrop, fileStatus }) => {
+    //по файлу в дропзоне:
+    //дергать onFileDrop
+    //fileStatus - информация о заливке файла из redux
+    //через useEffect дождаться когда файл зальется
+    // console.log('_id ', uploadFiles?.payload?.images?._id)
+    // console.log('url  ', uploadFiles?.payload?.images?.url)
+  
+    console.log('ON FILE DROP ', onFileDrop)
+  
+    const [state, setState] = useState(post)
+    useEffect(() => {
+      fileStatus?.status == 'FULFILLED' &&
+        setState({
+          ...state,
+          images: [
+            ...state.images,
+            {
+              
+              _id: fileStatus?.payload?._id,
+              url: fileStatus?.payload?.url,
+            },
+          ],
+        })
+        &&  console.log('_id ', fileStatus?.payload?._id)&&
+        console.log('url  ', fileStatus?.payload?.url)
+    }, [fileStatus])
+  
+    const onSortEnd = ({ oldIndex, newIndex }) => {
+      setState({
+        ...state,
+        images: arrayMoveImmutable(state.images, oldIndex, newIndex),
+      })
+    }
+    const onChangeTitle = (event) =>
+      setState({
+        ...state,
+        title: event.target.value,
+      })
+  
+    const onChangeText = (event) =>
+      setState({
+        ...state,
+        text: event.target.value,
+      })
+    const onRemoveImage = (_id) =>
+      setState({
+        ...state,
+        images: state.images.filter((item) => item._id !== _id),
+      })
+    return (
+      <section className="Post">
+        <Basic onLoad={onFileDrop} />
+        <SortableContainer onSortEnd={onSortEnd}>
+          {(state.images || []).map(({ _id, url }, index) => (
+            <>
+              <SortableItem key={`item-${_id}`} index={index} url={url} />
+              <button onClick={() => onRemoveImage(_id)}> x </button>
+            </>
+          ))}
+        </SortableContainer>
+        <h1 className="Title"> Title </h1>
+        <Input state={state.title || ''} onChangeText={onChangeTitle} />
+        <h1 className="Title"> Text </h1>
+  
+        <Input state={state.text || ''} onChangeText={onChangeText} />
+        <button
+          disabled={state?.images?.length == 0}
+          onClick={() => onSave(state)}
+        >
+          Save
+        </button>
+      </section>
+    )
+  }
+ export const CPostEditor = connect(
+    (state) => ({ fileStatus: state.promise?.uploadFile}),
+    {
+      onSave: actionPostUpsert,
+      onFileDrop: actionUploadFile,
+    },
+  )(PostEditor)

+ 138 - 0
src/components/Post.js

@@ -0,0 +1,138 @@
+import { Router, Route, Link, Redirect, Switch } from 'react-router-dom'
+import {backendURL,actionAllPosts,actionOnePost} from '../actions'
+import photoNotFound from '../materials/photoNotFound.png'
+import { LeftCircleFilled, RightCircleFilled } from '@ant-design/icons'
+import { Carousel,Avatar } from 'antd'
+import user from '../materials/user.png'
+import { Provider, connect } from 'react-redux'
+
+export const Card = ({ post, onPost }) => (
+  <>
+    <Link to={`/post/${post?._id}`} onClick={() => onPost(post?._id)}>
+      {post?.images && post?.images[0] && post.images[0]?.url ? (
+        <img
+          className="Card"
+          src={backendURL + '/' + post.images[0].url}
+          style={{ maxWidth: '200px', maxHeight: '200px' }}
+        />
+      ) : (
+        <img
+          className="Card"
+          src={photoNotFound}
+          style={{ maxWidth: '200px', maxHeight: '200px' }}
+        />
+      )}
+      {/* {console.log(post?._id)} */}
+    </Link>
+  </>
+)
+const SampleNextArrow = (props) => {
+  const { className, style, onClick } = props
+  return (
+    <div
+      className="carousel-control-next"
+      style={{
+        fontSize: '50px',
+        color: '#a8a8a8',
+        position: 'absolute',
+        left: '100%',
+        top: '50%',
+        margin: 'auto',
+      }}
+      onClick={onClick}
+    >
+      <RightCircleFilled />
+    </div>
+  )
+}
+
+const SamplePrevArrow = (props) => {
+  const { className, style, onClick } = props
+  return (
+    <div
+      className="carousel-control-prev"
+      style={{
+        color: '#a8a8a8',
+        fontSize: '50px',
+        position: 'absolute',
+        margin: 'auto',
+        right: '100%',
+        top: '50%'
+      }}
+      onClick={onClick}
+    >
+      <LeftCircleFilled />
+    </div>
+  )
+}
+
+const MyCarousel = ({ images = [] }) => {
+  console.log('IMAGES', images)
+  return (
+    <>
+      <div>
+        <Carousel
+          style={{
+            display: 'block',
+            minWidth: '500px',
+            minHeight: '500px',
+            background: 'blue',
+          }}
+          effect="fade"
+          arrows
+          nextArrow={<SampleNextArrow />}
+          prevArrow={<SamplePrevArrow />}
+        >
+          {images &&
+            images.map((i, index) =>
+              i?.url ? (
+                <div key={index}>
+                  <img
+                    className="PostImage"
+                    src={backendURL + '/' + i?.url}
+                    style={{
+                      display: 'flex',
+                      alignItems: 'center',
+                      maxWidth: '400px',
+                      maxHeight: '400px',
+                    }}
+                  />
+                </div>
+              ) : (
+                <div>
+                  <img
+                    className="PostImage"
+                    src={photoNotFound}
+                    style={{ maxWidth: '400px', maxHeight: '400px' }}
+                  />
+                </div>
+              ),
+            )}
+        </Carousel>
+      </div>
+    </>
+  )
+}
+const PagePost = ({ onePost, aboutMe: { avatar, login } = {}, onPost }) => {
+  return (
+    <>
+      <MyCarousel images={onePost?.images} />
+
+      {avatar ? (
+        <Avatar
+          style={{ width: '50px', height: '50px' }}
+          src={backendURL + '/' + avatar?.url}
+        />
+      ) : (
+        <Avatar style={{ width: '50px', height: '50px' }} src={user} />
+      )}
+      <h2> {onePost?.title || ''} </h2>
+      <h2> {onePost?.text || ''} </h2>
+    </>
+  )
+}
+
+export const CPost = connect((state) => ({
+  onePost: state.promise.onePost?.payload,
+  aboutMe: state.promise?.aboutMe?.payload,
+}))(PagePost)

+ 80 - 0
src/components/User.js

@@ -0,0 +1,80 @@
+import {backendURL,actionAllPosts,actionOnePost,actionAboutMe} from '../actions'
+import user from '../materials/user.png'
+import React, { useMemo, useState, useEffect } from 'react'
+import {Card} from '../components/Post'
+import { Provider, connect } from 'react-redux'
+import { Avatar, Image, Divider, Radio } from 'antd'
+import { store } from '../reducers'
+
+const PageAboutUser = ({
+    aboutMe: { _id, login, nick, createdAt, avatar, followers, following } = {},
+    allPosts,
+    onPosts,
+    onPost,
+    onAboutUser
+  }) => {
+    useEffect(() => {
+      // onAboutUser(_id)
+      onPosts()
+    }, [])
+
+    // console.log('CREATED AT',new Intl.DateTimeFormat().format(createdAt));
+    return (
+      <section className="AboutMe">
+        <Avatar
+          style={{ width: '150px', height: '150px', position: 'absolute' }}
+          src={backendURL + '/' + avatar?.url || user}
+        />
+        <div className="Info">
+          <h1> {login}</h1>
+          <h3>
+            {' '}
+            Created Account: {new Intl.DateTimeFormat('en-GB').format(createdAt)}
+          </h3>
+          <div style={{ display: 'flex' }}>
+            {/* {allPosts?.length} style={{display: 'flex',justifyContent: 'space-between'}}*/}
+            <h3> {allPosts?.length} posts </h3>
+  
+            <h3 style={{ marginLeft: '20px' }}>
+              {' '}
+              {followers?.length} followers{' '}
+            </h3>
+  
+            <h3 style={{ marginLeft: '20px' }}>
+              {' '}
+              {following?.length} following{' '}
+            </h3>
+          </div>
+          <h3> nick: {nick == null ? login : nick}</h3>
+          <div
+            style={{
+              display: 'flex',
+              flexWrap: 'wrap',
+              padding: '20px',
+              margin: '20px',
+            }}
+          >
+            {(allPosts || [])?.map((item) => (
+              <Card post={item} onPost={onPost} />
+            ))}
+          </div>
+          {/* <h3> Created Account: {
+              <div>
+              <img
+        src={backendURL + '/' + allPosts?.url}
+        style={{ maxWidth: '200px', maxHeight: '200px' }}/>
+        </div> date} 
+          </h3> */}
+        </div>
+      </section>
+    )
+  }
+ export const CPageAboutUser = connect(
+    (state) => ({
+      aboutMe: state.promise?.aboutMe?.payload,
+      allPosts: state.promise?.allPosts?.payload,
+    }),
+    { onPosts: actionAllPosts, onPost: actionOnePost,
+    // onAboutUser: actionAboutMe
+    },
+  )(PageAboutUser)