Kaynağa Gözat

added page Collection, refactoring

makstravm 2 yıl önce
ebeveyn
işleme
1d5a844cf9

+ 2 - 0
src/App.js

@@ -15,6 +15,7 @@ import { CEntityEditorPost } from './pages/EntityEditorPost';
 import { SettingsPage } from './pages/SettingsPage';
 import { Authorization } from './pages/Authorization';
 import { CPreloader } from './pages/Preloader';
+import { CCollectionPage } from './pages/CollectionPage';
 
 export const history = createHistory()
 
@@ -38,6 +39,7 @@ const AppContent = ({ isToken }) =>
                         <Route path='/edit/post/:_id' component={CEntityEditorPost} />
                         <Route path='/my-settings' component={SettingsPage} />
                         <Route path='/all' component={CAllPosts} />
+                        <Route path='/my-collection' component={CCollectionPage} />
                         <CRRoute path='/post/:id' component={CPostPage} />
                         <Redirect from='/*' to='/' />
                         {/* <Redirect from='/*' to='/post/:id' /> */}

+ 22 - 2
src/App.scss

@@ -153,6 +153,13 @@ video {
     a {
         transition: all 0.3s;
     }
+    &__icon {
+        margin-right: 2px;
+        svg {
+            width: 15px;
+            height: 15px;
+        }
+    }
     button {
         cursor: pointer;
         text-align-last: left;
@@ -258,6 +265,7 @@ video {
         top: 1px;
         width: 50px;
         transition: 0.4s;
+        z-index: 5;
         cursor: pointer;
         svg {
             fill: $defaultColorW;
@@ -281,8 +289,10 @@ video {
             opacity: 1;
         }
     }
-    &__heart {
+    &__heart,
+    &__collection {
         button {
+            display: inline-block;
             min-width: auto;
             width: 30px;
             height: 30px;
@@ -291,7 +301,17 @@ video {
             box-shadow: none;
         }
         strong {
-            display: block;
+            display: inline-block;
+        }
+    }
+    &__collection {
+        button {
+            margin-right: auto;
+        }
+        svg {
+            stroke-width: 2px;
+            width: 24px;
+            height: 24px;
         }
     }
     &__comments {

+ 41 - 3
src/actions/index.js

@@ -34,9 +34,10 @@ export const actionRegister = (login, password) =>
 
 //*************** Action ABOUT ME ******************//
 
-
 export const actionAboutMeAC = (data) => ({ type: 'ABOUTME-DATA-ADD', data })
 export const actionFullAboutMe = () => ({ type: 'ABOUT_ME' })
+export const actionRemoveMyDataAC = () => ({ type: 'REMOVE-MYDATA' })
+
 
 export const actionFullAboutMeUpsert = (nick, login) => ({ type: 'ABOUT_ME_UPSERT', nick, login })
 
@@ -111,8 +112,8 @@ export const actionProfileData = (_id) =>
                             avatar { _id url }     
                             createdAt
                             followers {_id }
-                            following {_id }
-                }
+                            following {_id }}
+                
             } `, { id: JSON.stringify([{ _id }]) }))
 
 export const actionProfilePagePost = (_id, skip) => actionPromise('userOneDataPosts', gql(` query userOned($id:String!){
@@ -212,6 +213,7 @@ export const actionMyLikePost = (findId) =>
 
 //****************---Action Like Comment ---*************************//
 
+
 export const actionAddLikeCommentAC = (findId, newResult) => ({ type: 'ADD-LIKE-COMMENT', findId, newResult })
 export const actionRemoveLikeCommentAC = (findId, newResult) => ({ type: 'REMOVE-LIKE-COMMENT', findId, newResult })
 
@@ -238,6 +240,42 @@ export const actionFindLikeComment = (findId) =>
     }`, { id: JSON.stringify([{ _id: findId }]) }))
 
 
+//****************---Collection---*************************//
+
+
+export const actionUpsertCollectionAC = (data) => ({ type: 'UPSERT-COLLECTION', data })
+export const actionHandlerUpsertCollection = (_id, flag) => ({ type: 'HANDLER_UPSERT_COLLECTION', _id, flag })
+
+export const actionAddPostInCollections = (collectionId, newCollection) =>
+    actionPromise('addInCollections', gql(`mutation addInCollections($collection:CollectionInput ){
+        CollectionUpsert(collection:$collection){
+            _id 
+        }
+    }`, { collection: { _id: collectionId, posts: newCollection } }))
+
+export const actionFindMyCollections = (_id) =>
+    actionPromise('findMyCollections', gql(`query findCollections($id:String! ){
+        CollectionFindOne(query:$id){
+        _id text posts{_id }
+        }
+    }`, { id: JSON.stringify([{ ___owner: _id }]) }))
+
+export const actionFullMyCollectionLoad = () => ({ type: 'LOAD_COLLECTION' })
+
+export const actionOnLoadMyCollection = (_id, skip) =>
+    actionPromise('onLoadMyCollections', gql(`query loadCollections($id:String! ){
+       CollectionFind(query:$id){
+         posts { _id  images{ _id url originalFileName}}
+        }
+    }`, {
+        id: JSON.stringify([{ _id }, {
+            sort: [{ _id: -1 }],
+            skip: [skip || 0],
+            limit: [36]
+        }])
+    }))
+
+//  posts{ images { url _id originalFileName } } 
 //****************---Action Subscribe ---*************************//
 
 

+ 27 - 1
src/components/main/Posts.jsx

@@ -32,4 +32,30 @@ const Posts = ({ posts }) =>
         </Col>)
         }
     </Row >
-export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)
+export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)
+
+
+// const Posts = ({ posts }) =>
+//     <Row gutter={[15, 15]}>
+//         {Array.isArray(posts) && posts.map(p => <Col key={p._id} span={8}>
+//             <Link to={`/post/${p._id}`}>
+//                 <Card className='Profile__post' hoverable>
+//                     {p?.url
+//                         ? videoRegExp.test(p?.originalFileName)
+//                             ? <div className='Profile__box' >
+//                                 <video>
+//                                     <source src={backURL + '/' + p?.url} />
+//                                 </video>
+//                                 <PlayCircleOutlined className='Profile__box-icon--video' />
+//                             </div>
+
+//                             : <img src={backURL + '/' + p?.url} />
+
+//                         : <img src={postNoData} />
+//                     }
+//                 </Card>
+//             </Link>
+//         </Col>)
+//         }
+//     </Row >
+// export const CPosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(Posts)

+ 28 - 13
src/components/main/postsFeed/PostUserPanel.jsx

@@ -2,7 +2,8 @@ import { HeartFilled, HeartOutlined } from "@ant-design/icons"
 import { Button, Col, Row, Tooltip } from "antd"
 import { useState } from "react"
 import { connect } from "react-redux"
-import { actionDelLikePost, actionLikePost } from "../../../actions"
+import { actionDelLikePost, actionHandlerUpsertCollection, actionLikePost, } from "../../../actions"
+import { CollectionEmptySvg, CollectionSvgFill } from "../../../helpers"
 import { CModalPostLiked } from "../profilePage/ModalFollow"
 
 
@@ -18,16 +19,17 @@ const HeartLike = ({ styleFontSize, likeStatus, changeLike }) =>
         }
     />
 
-const PostUserPanel = ({ myID, postId = '', likes = [], styleFontSize, addLikePost, removeLikePost }) => {
+const PostUserPanel = ({ myID, postId = '', likes = [], collections, styleFontSize, addLikePost, removeLikePost, handlerCollection }) => {
     const [open, setOpen] = useState(false)
-    let likeStatus =false
+    let likeStatus = false
     let likeId
     likes.find(l => {
         if (l?.owner?._id === myID) {
             likeStatus = true
             likeId = l._id
-        } 
+        }
     })
+    const flag = collections?.posts.find(c => c._id === postId)
 
     const changeLike = () => likeStatus ? removeLikePost(likeId, postId) : addLikePost(postId)
 
@@ -35,24 +37,37 @@ const PostUserPanel = ({ myID, postId = '', likes = [], styleFontSize, addLikePo
     return (
         <>
             {open && <CModalPostLiked statusModal={setOpen} title={'Liked'} id={postId} />}
-            <Row className="Post__panel-btn" align="middle">
-                <Col className='Post__heart'>
-                    <HeartLike
-                        changeLike={changeLike}
-                        likeStatus={likeStatus}
-                        styleFontSize={styleFontSize} />
+            <Row className="Post__panel-btn" justify="space-between" align="middle">
+                <Col flex={'50%'}>
+                    <Row align="middle" wrap={false}>
+                        <Col className='Post__heart' >
+                            <HeartLike
+                                changeLike={changeLike}
+                                likeStatus={likeStatus}
+                                styleFontSize={styleFontSize} />
+                        </Col>
+                        <Col offset={1}>
+                            {!!likes.length && <button onClick={() => { setOpen(true) }}>Likes:<strong>{` ${likes.length}`}</strong></button>}
+                        </Col>
+                    </Row>
                 </Col>
-                <Col offset={0.5}>
-                    {!!likes.length && <button onClick={() => { setOpen(true) }}>Likes:<strong>{` ${likes.length}`}</strong></button>}
+                <Col flex={'10%'} className='Post__collection'>
+                    <Button type="none"
+                        shape="circle" onClick={() => handlerCollection(postId, flag)}>
+                        {flag ? <CollectionSvgFill /> : <CollectionEmptySvg />}
+                    </Button>
                 </Col>
             </Row>
+
         </>
     )
 }
 
 export const CPostUserPanel = connect(state => ({
-    myID: state.auth.payload.sub.id || ''
+    myID: state.auth.payload.sub.id || '',
+    collections: state.myData?.collections
 }), {
     addLikePost: actionLikePost,
     removeLikePost: actionDelLikePost,
+    handlerCollection: actionHandlerUpsertCollection,
 })(PostUserPanel)

+ 1 - 1
src/components/uploadPhoto/EditDescriptionPost.js

@@ -14,7 +14,7 @@ export const EditDescriptionPost = ({ description, setDescription }) => {
     const [editMode, setEditMode] = useState(false)
 
     useEffect(() => {
-        setText(description || 'Enter descriptin')
+        setText(description || '')
     }, [description]);
 
     const addValueHandler = () => {

+ 1 - 0
src/components/uploadPhoto/EditPhotos.js

@@ -24,6 +24,7 @@ const SortableItemMask = ({ removePhotosItem, chekMedia, setVisible, id }) =>
 
 const Handle = SortableHandle(({ tabIndex, value, removePhotosItem }) => {
     const [visible, setVisible] = useState(false);
+    console.log(value);
     const chekMedia = videoRegExp.test(value.originalFileName)
     return (
         <div className="Handle" tabIndex={tabIndex} >

+ 2 - 2
src/components/uploadPhoto/EditTitlePost.js

@@ -9,12 +9,12 @@ import { useEffect, useState } from "react"
 
 export const EditTitlePost = ({ titleSend, setTitleSend }) => {
 
-    const [title, setTitle] = useState(titleSend || 'Enter title')
+    const [title, setTitle] = useState(titleSend || '')
     const [error, setError] = useState(false)
     const [editMode, setEditMode] = useState(false)
 
     useEffect(() => {
-        setTitle(titleSend || 'Enter title')
+        setTitle(titleSend || '')
     }, [titleSend]);
 
     const addValueHandler = () => {

+ 14 - 0
src/helpers/index.js

@@ -56,6 +56,20 @@ const CircularGallerySvg = () =>
 export const CircularGalleryIcon = props =>
     <Icon component={CircularGallerySvg} {...props} />
 
+const CollectionSvgEmpty = () =>
+    <svg  viewBox="0 0 24 24" ><polygon fill="none" points="20 21 12 13.44 4 21 4 3 20 3 20 21" stroke="currentColor"  strokeWidth="2"></polygon></svg>
+
+
+export const CollectionEmptySvg = props =>
+    <Icon component={CollectionSvgEmpty} {...props} />
+
+const CollectionFillSvg = () =>
+    <svg aria-label="Удалить" color="#262626" fill="#262626" height="24" role="img" viewBox="0 0 24 24" width="24"><path d="M20 22a.999.999 0 01-.687-.273L12 14.815l-7.313 6.912A1 1 0 013 21V3a1 1 0 011-1h16a1 1 0 011 1v18a1 1 0 01-1 1z"></path></svg>
+
+export const CollectionSvgFill = props =>
+    <Icon component={CollectionFillSvg} {...props} />
+
+
 const RRoute = ({ action, component: Component, ...routeProps }) => {
     const WrapperComponent = (componentProps) => {
         useEffect(() => {

+ 2 - 2
src/pages/AllPosts.jsx

@@ -5,7 +5,7 @@ import { CPosts } from '../components/main/Posts';
 import { Container } from './Content';
 import { CPreloader } from './Preloader';
 
-const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
+const AllPosts = ({ onAllPosts, postsRemove }) => {
     const [checkScroll, setCheckScroll] = useState(true)
 
     useEffect(() => {
@@ -37,4 +37,4 @@ const AllPosts = ({ posts, onAllPosts, postsRemove }) => {
     )
 }
 
-export const CAllPosts = connect(state => ({ posts: state?.postsFeed?.posts || [] }), { onAllPosts: actionAllPosts, postsRemove: actionRemovePostsFeedAC, })(AllPosts)
+export const CAllPosts = connect(null, { onAllPosts: actionAllPosts, postsRemove: actionRemovePostsFeedAC, })(AllPosts)

+ 42 - 0
src/pages/CollectionPage.jsx

@@ -0,0 +1,42 @@
+import { Divider } from 'antd';
+import Title from 'antd/lib/typography/Title';
+import React, { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { actionFullMyCollectionLoad, actionRemovePostsFeedAC } from '../actions';
+import { CPosts } from '../components/main/Posts';
+import { Container } from './Content';
+import { CPreloader } from './Preloader';
+
+export const CollectionPage = ({ onLoadPosts, postsRemove }) => {
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(() => {
+        if (checkScroll) {
+            onLoadPosts()
+            setCheckScroll(false)
+        }
+    }, [checkScroll])
+
+    useEffect(() => {
+        document.addEventListener('scroll', scrollHandler)
+        return () => {
+            document.removeEventListener('scroll', scrollHandler)
+            postsRemove()
+        }
+    }, [])
+
+    const scrollHandler = (e) => {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+            setCheckScroll(true)
+        }
+    }
+
+    return (
+        <Container>
+            <CPreloader promiseName='onLoadMyCollections' />
+            <Divider><Title level={1}>Collections</Title></Divider>
+            <CPosts /> 
+        </Container>
+    )
+}
+export const CCollectionPage = connect(state => ({ posts: state?.postsFeed?.posts || [] }), { onLoadPosts: actionFullMyCollectionLoad, postsRemove: actionRemovePostsFeedAC })(CollectionPage)

+ 17 - 10
src/pages/Header.jsx

@@ -3,39 +3,46 @@ import logo from '../logo.svg';
 import { Link } from 'react-router-dom';
 import { CFieldSearch } from '../components/header/Search';
 import { connect } from 'react-redux';
-import { actionAuthLogout } from '../actions';
+import { actionAuthLogout, actionRemoveMyDataAC } from '../actions';
 import Layout, { Header } from 'antd/lib/layout/layout';
 import { Col, Menu, Popover, Row } from 'antd';
 import { UserOutlined, CompassOutlined, SettingOutlined, HomeOutlined, ImportOutlined, MessageOutlined, PlusCircleOutlined } from '@ant-design/icons/lib/icons';
 import { UserAvatar } from '../components/header/UserAvatar';
+import { CollectionEmptySvg } from '../helpers';
+
+
+
 
-import { history } from '../App'
 const UserNav = () =>
     <div className='UserNav'>
         <CUserNavIcon />
     </div>
 
-
-// console.log(history);
-const ProfileDropMenu = ({ myID, onLogOut }) =>
+const ProfileDropMenu = ({ myID, onLogOut, removeMydata }) =>
     <Menu className='dropMenu'>
         <Menu.Item key={'0'}>
             <Link to={`/profile/${myID}`}><UserOutlined /> My Profile</Link>
         </Menu.Item>
         <Menu.Item key={'1'}>
+            <Link to={'/my-collection'}>< CollectionEmptySvg className='dropMenu__icon' />Collection</Link>
+        </Menu.Item>
+        <Menu.Item key={'2'}>
             <Link to={'/my-settings'}><SettingOutlined /> Settings</Link>
         </Menu.Item>
         <Menu.Divider />
-        <Menu.Item key={'2'}>
-            <button onClick={() => onLogOut()}><ImportOutlined /> Log out</button>
+        <Menu.Item key={'3'}>
+            <button onClick={() => {
+                onLogOut()
+                removeMydata()
+            }}><ImportOutlined /> Log out</button>
         </Menu.Item>
     </Menu>
 
-const CProfileDropMenu = connect(null, { onLogOut: actionAuthLogout })(ProfileDropMenu)
+const CProfileDropMenu = connect(null, { onLogOut: actionAuthLogout, removeMydata: actionRemoveMyDataAC })(ProfileDropMenu)
 
 
 const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
-    <Row justify="end" align="middle" className='Header__userNav'>
+    < Row justify="end" align="middle" className='Header__userNav' >
         <Col >
             <Link to='/'><HomeOutlined /></Link>
         </Col>
@@ -49,7 +56,7 @@ const UserNavIcon = ({ userData: { _id, avatar, login } }) =>
             <Link to='/all'><CompassOutlined /></Link>
         </Col>
         <Col>
-            <Popover placement="bottomRight" content={<CProfileDropMenu myID={_id} />} trigger={['focus', 'hover']}>
+            <Popover placement="bottomRight" content={<CProfileDropMenu myID={_id} />}>
                 <></>
                 <UserAvatar avatar={avatar} login={login} avatarSize={'45px'} />
             </Popover>

+ 5 - 2
src/pages/MainPostsFeed.jsx

@@ -37,13 +37,16 @@ export const Comments = ({ comments = [], _id }) =>
         </Divider>
     </Link>
 
-const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes } }) =>
+const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes, collections } }) =>
     <div className='Post'>
         <Card
             title={<CPostTitle owner={owner} postId={_id} />}
             cover={<PostImage images={images} />}
         >
-            <CPostUserPanel postId={_id} likes={likes} styleFontSize='1.7em' />
+            <CPostUserPanel postId={_id}
+                likes={likes}
+                collections={collections}
+                styleFontSize='1.7em' />
             <PostDescription title={title} description={text} date={createdAt} />
             <Comments comments={comments} _id={_id} />
         </Card>

+ 1 - 1
src/pages/Preloader.jsx

@@ -7,7 +7,7 @@ const PreloaderImg = () =>
         <Spin size="large" />
     </div>
 
-const Preloader = ({ promiseName, promiseState, children }) =>
+const Preloader = ({ promiseName, promiseState}) =>
     <>
         {promiseState[promiseName]?.status === 'PENDING'
             ? <PreloaderImg />

+ 9 - 12
src/pages/SettingsPage.jsx

@@ -13,10 +13,10 @@ const ContainerSettingsPage = ({ children }) =>
     <div className="SettingsPage ContainerInner">{children}</div>
 
 
-const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
+const EditMyDataIput = ({ title, propValue, propHandler, error, setError, setChekEdit }) => {
     const [value, setValue] = useState(propValue);
     const [editMode, setEditMode] = useState(false);
-    const [error, setError] = useState(false);
+
 
     useEffect(() => {
         setValue(propValue)
@@ -24,10 +24,9 @@ const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
 
     const addValueHandler = () => {
         const valid = /^[A-Z][a-z0-9_]{1,15}$/
-        if (!valid.test(value)) {
+        if (valid.test(value)) {
             propHandler(value)
             setEditMode(false)
-            check(false)
         } else {
             setError(true)
         }
@@ -36,6 +35,7 @@ const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
     const onChangeInput = (e) => {
         setValue(e.currentTarget.value)
         setError(false)
+        setChekEdit(false)
     }
 
     return (
@@ -61,9 +61,9 @@ const EditMyDataIput = ({ title, propValue, propHandler, check }) => {
 }
 
 const EditMyData = ({ myData, status, onUpsert }) => {
-
     const [nick, setNick] = useState(myData?.nick || '');
     const [login, setLogin] = useState(myData?.login || '');
+    const [error, setError] = useState(false);
     const [checkEdit, setChekEdit] = useState(true);
     useEffect(() => {
         setNick(myData?.nick || '')
@@ -80,7 +80,7 @@ const EditMyData = ({ myData, status, onUpsert }) => {
                 },
             });
             setChekEdit(true)
-        } else if(status=== 'REJECTED'){
+        } else if (status === 'REJECTED') {
             message.error({
                 content: 'Что-то не так, нужно повторить!',
                 className: 'custom-class',
@@ -93,15 +93,12 @@ const EditMyData = ({ myData, status, onUpsert }) => {
 
     const onSendEdit = () => onUpsert(nick, login)
 
-
-
-
     return (
         <>
-            <EditMyDataIput title='Nick' propValue={nick} propHandler={setNick} check={setChekEdit} />
-            <EditMyDataIput title='Login' propValue={login} propHandler={setLogin} check={setChekEdit} />
+            <EditMyDataIput title='Nick' propValue={nick} propHandler={setNick} setChekEdit={setChekEdit} error={error} setError={setError} />
+            <EditMyDataIput title='Login' error={error} setError={setError} propValue={login} propHandler={setLogin} setChekEdit={setChekEdit} error={error} setError={setError} />
             <Button type='primary'
-                disabled={nick && login ? false : true}
+                disabled={!error ? false : true}
                 className={!checkEdit && '--block'}
                 onClick={onSendEdit}> SendEdit</Button>
         </>

+ 5 - 1
src/redux/reducers/myProfile-reducer.js

@@ -10,7 +10,11 @@ export const myProfileReducer = (state = {}, { type, data }) => {
         },
         'UPDATE-MY-FOLLOWING': () => {
             return { ...state, following: [...data] }
-        }
+        },
+        'UPSERT-COLLECTION': () => ({
+            ...state, collections: data
+        }),
+        'REMOVE-MYDATA': () => ({})
     }
     if (type in types) {
         return types[type]()

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

@@ -6,7 +6,7 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
         //=== Array.isArray(newResult)
         'ADD-POSTS-FEED': () => ({
             ...state,
-            posts: Array.isArray(newResult) 
+            posts: Array.isArray(newResult)
                 ? [...posts, ...newResult]
                 : { ...posts, ...newResult },
             count
@@ -65,7 +65,8 @@ export const postsFeedReducer = (state = {}, { type, findId, newResult, userData
         'UPDATE-FOLLOWERS': () => ({
             ...state,
             userData: { ...state.userData, followers: [...newResult] }
-        })
+        }),
+      
 
     }
     if (type in types) {

Dosya farkı çok büyük olduğundan ihmal edildi
+ 38 - 6
src/redux/saga/index.js