diff --git a/packages/concordia-app/src/components/PostCreate/index.jsx b/packages/concordia-app/src/components/PostCreate/index.jsx index 124696c..0047247 100644 --- a/packages/concordia-app/src/components/PostCreate/index.jsx +++ b/packages/concordia-app/src/components/PostCreate/index.jsx @@ -8,20 +8,31 @@ import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import determineKVAddress from '../../utils/orbitUtils'; -import { USER_DATABASE } from '../../constants/OrbitDatabases'; +import { POSTS_DATABASE, USER_DATABASE } from '../../constants/OrbitDatabases'; import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions'; import { USER_PROFILE_PICTURE } from '../../constants/UserDatabaseKeys'; -import { breeze } from '../../redux/store'; +import { breeze, drizzle } from '../../redux/store'; import './styles.css'; +import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../constants/TransactionStatus'; +import { POST_CONTENT, POST_SUBJECT } from '../../constants/PostsDatabaseKeys'; +const { contracts: { Forum: { methods: { createPost } } } } = drizzle; const { orbit } = breeze; const PostCreate = (props) => { - const { id: postId, initialPostSubject } = props; + const { + topicId, postIndexInTopic, initialPostSubject, account, + } = props; + const transactionStack = useSelector((state) => state.transactionStack); + const transactions = useSelector((state) => state.transactions); const [postSubject, setPostSubject] = useState(initialPostSubject); const [postContent, setPostContent] = useState(''); const [userProfilePictureUrl, setUserProfilePictureUrl] = useState(); + const [postSubjectInputEmptySubmit, setPostSubjectInputEmptySubmit] = useState(false); + const [postContentInputEmptySubmit, setPostContentInputEmptySubmit] = useState(false); + const [createPostCacheSendStackId, setCreatePostCacheSendStackId] = useState(''); const [posting, setPosting] = useState(false); + const [storingPost, setStoringPost] = useState(false); const userAddress = useSelector((state) => state.user.address); const users = useSelector((state) => state.orbitData.users); const dispatch = useDispatch(); @@ -68,13 +79,58 @@ const PostCreate = (props) => { } }, [posting]); + useEffect(() => { + if (posting && !storingPost && transactionStack && transactionStack[createPostCacheSendStackId] + && transactions[transactionStack[createPostCacheSendStackId]]) { + if (transactions[transactionStack[createPostCacheSendStackId]].status === TRANSACTION_ERROR) { + setPosting(false); + } else if (transactions[transactionStack[createPostCacheSendStackId]].status === TRANSACTION_SUCCESS) { + const { + receipt: { events: { PostCreated: { returnValues: { postID: contractPostId } } } }, + } = transactions[transactionStack[createPostCacheSendStackId]]; + + const { stores } = orbit; + const postsDb = Object.values(stores).find((store) => store.dbname === POSTS_DATABASE); + + postsDb + .put(contractPostId, { + [POST_SUBJECT]: postSubject, + [POST_CONTENT]: postContent, + }, { pin: true }) + .then(() => { + setPostSubject(initialPostSubject); + setPostContent(''); + setPosting(false); + setPostSubjectInputEmptySubmit(false); + setPostContentInputEmptySubmit(false); + setCreatePostCacheSendStackId(''); + }) + .catch((reason) => { + console.log(reason); + }); + + setStoringPost(true); + } + } + }, [ + createPostCacheSendStackId, initialPostSubject, postContent, postSubject, posting, storingPost, transactionStack, + transactions, + ]); + const savePost = useCallback(() => { - if (postSubject === '' || postContent === '') { + if (postSubject === '') { + setPostSubjectInputEmptySubmit(true); + return; + } + + if (postContent === '') { + setPostContentInputEmptySubmit(true); return; } setPosting(true); - }, [postContent, postSubject]); + setCreatePostCacheSendStackId(createPost.cacheSend(...[topicId], { from: account })); + }, [account, postContent, postSubject, topicId]); return ( @@ -93,7 +149,6 @@ const PostCreate = (props) => { size="big" inverted color="black" - verticalAlign="middle" /> )} @@ -105,11 +160,12 @@ const PostCreate = (props) => { name="postSubject" className="subject-input" size="mini" + error={postSubjectInputEmptySubmit} value={postSubject} onChange={handleInputChange} /> - {t('post.list.row.post.id', { id: postId })} + {t('post.list.row.post.id', { id: postIndexInTopic })} @@ -121,6 +177,7 @@ const PostCreate = (props) => { className="content-input" size="mini" rows={4} + error={postContentInputEmptySubmit} value={postContent} onChange={handleInputChange} /> @@ -133,8 +190,8 @@ const PostCreate = (props) => { animated type="button" color="green" - disabled={posting} - onClick={savePost || postSubject === '' || postContent === ''} + disabled={posting || postSubject === '' || postContent === ''} + onClick={savePost} > {t('post.create.form.send.button')} @@ -152,7 +209,8 @@ const PostCreate = (props) => { }; PostCreate.propTypes = { - id: PropTypes.number.isRequired, + topicId: PropTypes.number.isRequired, + postIndexInTopic: PropTypes.number.isRequired, initialPostSubject: PropTypes.string.isRequired, }; diff --git a/packages/concordia-app/src/components/PostList/PostListRow/index.jsx b/packages/concordia-app/src/components/PostList/PostListRow/index.jsx index af1a69a..085c217 100644 --- a/packages/concordia-app/src/components/PostList/PostListRow/index.jsx +++ b/packages/concordia-app/src/components/PostList/PostListRow/index.jsx @@ -7,8 +7,8 @@ import { import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import moment from 'moment'; -import { useHistory } from 'react-router'; import { useDispatch, useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { breeze } from '../../../redux/store'; import './styles.css'; @@ -21,7 +21,9 @@ import { FORUM_CONTRACT } from '../../../constants/ContractNames'; const { orbit } = breeze; const PostListRow = (props) => { - const { id: postId, postCallHash, loading } = props; + const { + id: postId, postIndexInTopic, postCallHash, loading, + } = props; const getPostResults = useSelector((state) => state.contracts[FORUM_CONTRACT].getPost); const [postAuthorAddress, setPostAuthorAddress] = useState(null); const [postAuthor, setPostAuthor] = useState(null); @@ -33,7 +35,6 @@ const PostListRow = (props) => { const posts = useSelector((state) => state.orbitData.posts); const users = useSelector((state) => state.orbitData.users); const dispatch = useDispatch(); - const history = useHistory(); const { t } = useTranslation(); useEffect(() => { @@ -89,25 +90,38 @@ const PostListRow = (props) => { } }, [postAuthorAddress, users]); + const authorAvatar = useMemo(() => (postAuthorMeta !== null && postAuthorMeta[USER_PROFILE_PICTURE] + ? ( + + ) + : ( + + )), [postAuthorMeta]); + + const authorAvatarLink = useMemo(() => { + if (postAuthorAddress) { + return ( + + {authorAvatar} + + ); + } + + return authorAvatar; + }, [authorAvatar, postAuthorAddress]); + return useMemo(() => ( - {postAuthorMeta !== null && postAuthorMeta[USER_PROFILE_PICTURE] - ? ( - - ) - : ( - - )} + {authorAvatarLink} @@ -116,15 +130,15 @@ const PostListRow = (props) => { ? postSubject : } - {t('post.list.row.post.id', { id: postId })} + {t('post.list.row.post.id', { id: postIndexInTopic })} - {postAuthor !== null && timeAgo !== null + {postAuthor !== null && setPostAuthorAddress !== null && timeAgo !== null ? ( <> {t('post.list.row.author.pre')}   - {postAuthor} + {postAuthor} {timeAgo} ) @@ -135,7 +149,9 @@ const PostListRow = (props) => { - ), [loading, postAuthor, postAuthorMeta, postId, postContent, postSubject, t, timeAgo]); + ), [ + authorAvatarLink, loading, postAuthor, postAuthorAddress, postContent, postIndexInTopic, postSubject, t, timeAgo, + ]); }; PostListRow.defaultProps = { @@ -144,6 +160,7 @@ PostListRow.defaultProps = { PostListRow.propTypes = { id: PropTypes.number.isRequired, + postIndexInTopic: PropTypes.number.isRequired, postCallHash: PropTypes.string, loading: PropTypes.bool, }; diff --git a/packages/concordia-app/src/components/PostList/index.jsx b/packages/concordia-app/src/components/PostList/index.jsx index c5c8130..68759b2 100644 --- a/packages/concordia-app/src/components/PostList/index.jsx +++ b/packages/concordia-app/src/components/PostList/index.jsx @@ -41,12 +41,13 @@ const PostList = (props) => { return null; } return postIds - .map((postId) => { + .map((postId, index) => { const postHash = getPostCallHashes.find((getPostCallHash) => getPostCallHash.id === postId); return ( { }, [getPostCallHashes, loading, postIds]); return ( - + {posts} diff --git a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx index 05974ee..4d25f91 100644 --- a/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx +++ b/packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx @@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next'; import moment from 'moment'; import { useHistory } from 'react-router'; import { useDispatch, useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { breeze } from '../../../redux/store'; import './styles.css'; @@ -89,6 +90,40 @@ const TopicListRow = (props) => { } }, [topicAuthorAddress, users]); + const stopClickPropagation = (event) => { + event.stopPropagation(); + }; + + const authorAvatar = useMemo(() => (topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] + ? ( + + ) + : ( + + )), [topicAuthorMeta]); + + const authorAvatarLink = useMemo(() => { + if (topicAuthorAddress) { + return ( + + {authorAvatar} + + ); + } + + return authorAvatar; + }, [authorAvatar, topicAuthorAddress]); + return useMemo(() => { const handleTopicClick = () => { history.push(`/topics/${topicId}`); @@ -96,23 +131,7 @@ const TopicListRow = (props) => { return ( - {topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] - ? ( - - ) - : ( - - )} + {authorAvatarLink} @@ -149,7 +168,7 @@ const TopicListRow = (props) => { ); - }, [history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicAuthorMeta, topicId, topicSubject]); + }, [authorAvatarLink, history, loading, numberOfReplies, t, timeAgo, topicAuthor, topicId, topicSubject]); }; TopicListRow.defaultProps = { diff --git a/packages/concordia-app/src/options/web3Options.js b/packages/concordia-app/src/options/web3Options.js index 49d20fa..1957de8 100644 --- a/packages/concordia-app/src/options/web3Options.js +++ b/packages/concordia-app/src/options/web3Options.js @@ -3,7 +3,9 @@ import Web3 from 'web3'; const { WEB3_URL, WEB3_PORT } = process.env; // We need fallback ws://127.0.0.1:8545 because drizzle has not the patched web3 we use here -const web3 = (WEB3_URL && WEB3_PORT) ? `ws://${WEB3_URL}:${WEB3_PORT}` : new Web3(Web3.givenProvider || new Web3.providers.WebsocketProvider('ws://127.0.0.1:8545')); +const web3 = (WEB3_URL && WEB3_PORT) + ? `ws://${WEB3_URL}:${WEB3_PORT}` + : new Web3(Web3.givenProvider || new Web3.providers.WebsocketProvider('ws://127.0.0.1:8545')); const web3Options = { web3, diff --git a/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js index cd54388..5e30042 100644 --- a/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js +++ b/packages/concordia-app/src/redux/sagas/peerDbReplicationSaga.js @@ -31,7 +31,7 @@ function* updateReduxState({ database }) { if (database.dbname === USER_DATABASE) { const oldUsersUnchanged = users - .filter((user) => !database.id !== user.id); + .filter((user) => database.id !== user.id); yield put({ type: UPDATE_ORBIT_DATA, diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx index 5f747c2..c3fa8cf 100644 --- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx +++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx @@ -5,6 +5,8 @@ import { Container, Dimmer, Icon, Image, Placeholder, Step, } from 'semantic-ui-react'; import moment from 'moment'; +import { Link } from 'react-router-dom'; +import { useHistory } from 'react-router'; import { breeze, drizzle } from '../../../redux/store'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import './styles.css'; @@ -37,7 +39,7 @@ const TopicView = (props) => { const [timestamp, setTimestamp] = useState(initialTimestamp || null); const [postIds, setPostIds] = useState(initialPostIds || null); const [topicSubject, setTopicSubject] = useState(null); - + const history = useHistory(); const dispatch = useDispatch(); useEffect(() => { @@ -55,6 +57,11 @@ const TopicView = (props) => { useEffect(() => { if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) { + if (getTopicResults[getTopicCallHash].value == null) { + history.push('/'); + return; + } + setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]); setTopicAuthor(getTopicResults[getTopicCallHash].value[1]); setTimestamp(getTopicResults[getTopicCallHash].value[2]); @@ -72,7 +79,7 @@ const TopicView = (props) => { }); } } - }, [dispatch, getTopicCallHash, getTopicResults, topicId, topics, userAddress]); + }, [dispatch, getTopicCallHash, getTopicResults, history, topicId, topics, userAddress]); useEffect(() => { if (topicAuthorAddress !== null) { @@ -115,28 +122,32 @@ const TopicView = (props) => { > - {topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] - ? ( - - ) - : ( - - )} + + {topicAuthorMeta !== null && topicAuthorMeta[USER_PROFILE_PICTURE] + ? ( + + ) + : ( + + )} + - {topicAuthor || ( - - - - )} + + {topicAuthor || ( + + + + )} + @@ -165,7 +176,8 @@ const TopicView = (props) => { {topicSubject !== null && postIds !== null && ( )}