diff --git a/app/src/CustomPropTypes.js b/app/src/CustomPropTypes.js new file mode 100644 index 0000000..204d72f --- /dev/null +++ b/app/src/CustomPropTypes.js @@ -0,0 +1,11 @@ +import PropTypes from 'prop-types'; + +const GetTopicResult = PropTypes.PropTypes.shape({ + 0: PropTypes.string, + 1: PropTypes.string, + 2: PropTypes.string, + 3: PropTypes.string, + 4: PropTypes.arrayOf(PropTypes.number) +}); + +export { GetTopicResult }; diff --git a/app/src/components/FloatingButton.js b/app/src/components/FloatingButton.js index 6377b10..891e35a 100644 --- a/app/src/components/FloatingButton.js +++ b/app/src/components/FloatingButton.js @@ -1,12 +1,17 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Button, Icon } from 'semantic-ui-react'; -const FloatingButton = props => ( -
+const FloatingButton = ({ onClick }) => ( +
); +FloatingButton.propTypes = { + onClick: PropTypes.func.isRequired +}; + export default FloatingButton; diff --git a/app/src/components/LoadingSpinner.js b/app/src/components/LoadingSpinner.js index cf88d11..2a95154 100644 --- a/app/src/components/LoadingSpinner.js +++ b/app/src/components/LoadingSpinner.js @@ -1,11 +1,12 @@ import React from 'react'; +import PropTypes from 'prop-types'; -const LoadingSpinner = props => ( +const LoadingSpinner = ({ className, style }) => (

@@ -14,4 +15,9 @@ const LoadingSpinner = props => (

); +LoadingSpinner.propTypes = { + className: PropTypes.string, + style: PropTypes.string +}; + export default LoadingSpinner; diff --git a/app/src/components/NewPost.js b/app/src/components/NewPost.js index 746ae98..8bb0f8c 100644 --- a/app/src/components/NewPost.js +++ b/app/src/components/NewPost.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Button, Divider, Form, Grid, Icon, TextArea } from 'semantic-ui-react'; @@ -10,8 +11,9 @@ import ReactMarkdown from 'react-markdown'; import { createPost } from '../redux/actions/transactionsActions'; class NewPost extends Component { - constructor(props, context) { + constructor(props) { super(props); + const { subject } = props; this.handleInputChange = this.handleInputChange.bind(this); this.handlePreviewToggle = this.handlePreviewToggle.bind(this); @@ -20,7 +22,7 @@ class NewPost extends Component { this.newPostOuterRef = React.createRef(); this.state = { - postSubjectInput: this.props.subject ? this.props.subject : '', + postSubjectInput: subject ? subject : '', postContentInput: '', postSubjectInputEmptySubmit: false, postContentInputEmptySubmit: false, @@ -29,24 +31,41 @@ class NewPost extends Component { }; } + componentDidMount() { + this.newPostOuterRef.current.scrollIntoView(true); + } + + getDate() { + const currentdate = new Date(); + return (`${currentdate.getMonth() + 1} ${ + currentdate.getDate()}, ${ + currentdate.getFullYear()}, ${ + currentdate.getHours()}:${ + currentdate.getMinutes()}:${ + currentdate.getSeconds()}`); + } + async validateAndPost() { - if (this.state.postSubjectInput === '' || this.state.postContentInput + const { postSubjectInput, postContentInput } = this.state; + const { topicID, onPostCreated, dispatch } = this.props; + + if (postSubjectInput === '' || postContentInput === '') { this.setState({ - postSubjectInputEmptySubmit: this.state.postSubjectInput === '', - postContentInputEmptySubmit: this.state.postContentInput === '' + postSubjectInputEmptySubmit: postSubjectInput === '', + postContentInputEmptySubmit: postContentInput === '' }); return; } - this.props.dispatch( - createPost(this.props.topicID, + dispatch( + createPost(topicID, { - postSubject: this.state.postSubjectInput, - postMessage: this.state.postContentInput + postSubject: postSubjectInput, + postMessage: postContentInput }), ); - this.props.onPostCreated(); + onPostCreated(); } handleInputChange(event) { @@ -56,29 +75,25 @@ class NewPost extends Component { } handlePreviewToggle() { - this.setState((prevState, props) => ({ + this.setState(prevState => ({ previewEnabled: !prevState.previewEnabled, previewDate: this.getDate() })); } - getDate() { - const currentdate = new Date(); - return (`${currentdate.getMonth() + 1} ${ - currentdate.getDate()}, ${ - currentdate.getFullYear()}, ${ - currentdate.getHours()}:${ - currentdate.getMinutes()}:${ - currentdate.getSeconds()}`); - } - render() { + const { + previewDate, postSubjectInputEmptySubmit, postSubjectInput, postContentInputEmptySubmit, + postContentInput, previewEnabled + } = this.state; + const { postIndex, avatarUrl, user, onCancelClick } = this.props; + return (
# - {this.props.postIndex} + {postIndex} @@ -87,39 +102,39 @@ class NewPost extends Component {
- {this.props.user.username} + {user.username} - {this.state.previewEnabled - && + {previewEnabled + && }
- {this.state.previewEnabled + {previewEnabled && (`Subject: ${ - this.state.postSubjectInput}`) + postSubjectInput}`) }
@@ -127,14 +142,14 @@ class NewPost extends Component { - {this.state.previewEnabled ? 'Edit' : 'Preview'} + {previewEnabled ? 'Edit' : 'Preview'}
); } - - componentDidMount() { - this.newPostOuterRef.current.scrollIntoView(true); - } } +NewPost.propTypes = { + subject: PropTypes.string, + topicID: PropTypes.number.isRequired, + postIndex: PropTypes.number.isRequired, + avatarUrl: PropTypes.string, + user: PropTypes.object.isRequired, + onCancelClick: PropTypes.func.isRequired, + dispatch: PropTypes.func.isRequired, + onPostCreated: PropTypes.func.isRequired +}; + const mapStateToProps = state => ({ orbitDB: state.orbitDB, user: state.user diff --git a/app/src/components/NewTopicPreview.js b/app/src/components/NewTopicPreview.js index c003bb9..167c210 100644 --- a/app/src/components/NewTopicPreview.js +++ b/app/src/components/NewTopicPreview.js @@ -1,4 +1,5 @@ -import React, { Component } from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Divider, Grid } from 'semantic-ui-react'; @@ -7,59 +8,58 @@ import TimeAgo from 'react-timeago'; import UserAvatar from 'react-user-avatar'; import ReactMarkdown from 'react-markdown'; -class Post extends Component { - constructor(props, context) { - super(props); - } +const Post = ({ user, date, subject, content }) => ( +
+ + #0 + + + + + + + +
+
+ + + {user.username} + + + + + +
+
+ + + Subject: + {' '} + {subject} + + +
+
+ +
+
+
+
+
+
+); - render() { - return ( -
- - #0 - - - - - - - -
-
- - - {this.props.user.username} - - - - - -
-
- - - Subject: - {' '} - {this.props.subject} - - -
-
- -
-
-
-
-
-
- ); - } -} +Post.propTypes = { + subject: PropTypes.string, + date: PropTypes.string, + content: PropTypes.string, + user: PropTypes.object.isRequired +}; const mapStateToProps = state => ({ user: state.user diff --git a/app/src/components/Post.js b/app/src/components/Post.js index ef74be5..abd8b39 100644 --- a/app/src/components/Post.js +++ b/app/src/components/Post.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { push } from 'connected-react-router'; import { Link, withRouter } from 'react-router-dom'; @@ -16,9 +17,11 @@ class Post extends Component { constructor(props) { super(props); + const { getFocus } = props; + this.getBlockchainData = this.getBlockchainData.bind(this); this.fetchPost = this.fetchPost.bind(this); - if (props.getFocus) { + if (getFocus) { this.postRef = React.createRef(); } @@ -31,24 +34,55 @@ class Post extends Component { }; } + componentDidMount() { + this.getBlockchainData(); + } + + componentDidUpdate() { + this.getBlockchainData(); + const { readyForAnimation } = this.state; + if (readyForAnimation) { + if (this.postRef) { + setTimeout(() => { + this.postRef.current.scrollIntoView( + { + block: 'start', behavior: 'smooth' + }, + ); + setTimeout(() => { + this.setState({ + animateOnToggle: false + }); + }, 300); + }, 100); + this.setState({ + readyForAnimation: false + }); + } + } + } + getBlockchainData() { - if (this.props.postData - && this.props.orbitDB.orbitdb - && this.state.fetchPostDataStatus === 'pending') { + const { fetchPostDataStatus } = this.state; + const { postData, orbitDB, postID } = this.props; + + if (postData && orbitDB.orbitdb && fetchPostDataStatus === 'pending') { this.setState({ fetchPostDataStatus: 'fetching' }); - this.fetchPost(this.props.postID); + this.fetchPost(postID); } } async fetchPost(postID) { + const { user, postData, orbitDB } = this.props; let orbitPostData; - if (this.props.postData.value[1] === this.props.user.address) { - orbitPostData = this.props.orbitDB.postsDB.get(postID); + + if (postData.value[1] === user.address) { + orbitPostData = orbitDB.postsDB.get(postID); } else { - const fullAddress = `/orbitdb/${this.props.postData.value[0]}/posts`; - const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); + const fullAddress = `/orbitdb/${postData.value[0]}/posts`; + const store = await orbitDB.orbitdb.keyvalue(fullAddress); await store.load(); const localOrbitData = store.get(postID); @@ -71,13 +105,16 @@ class Post extends Component { } render() { - const avatarView = (this.props.postData + const { animateOnToggle, postSubject, postContent } = this.state; + const { avatarUrl, postIndex, navigateTo, postData, postID } = this.props; + + const avatarView = (postData ? ( ) : ( @@ -99,23 +136,23 @@ class Post extends Component {
# - {this.props.postIndex} + {postIndex} - {this.props.postData !== null + {postData !== null ? ( { event.stopPropagation(); }} > {avatarView} @@ -127,21 +164,21 @@ class Post extends Component {
- - {this.props.postData !== null - ? this.props.postData.value[2] + {postData !== null + ? postData.value[2] : 'Username' } - {this.props.postData !== null + {postData !== null && ( ) @@ -150,11 +187,11 @@ class Post extends Component {
- {this.state.postSubject === '' + {postSubject === '' ? ( ) : `Subject: ${ - this.state.postSubject}` + postSubject}` }
- {this.state.postContent !== '' - ? + {postContent !== '' + ? : ( { - this.props.navigateTo(`/topic/${ - this.props.postData.value[4]}/${ - this.props.postID}`); + navigateTo(`/topic/${ + postData.value[4]}/${ + postID}`); } : () => {}} > @@ -248,35 +285,19 @@ class Post extends Component { ); } - - componentDidMount() { - this.getBlockchainData(); - } - - componentDidUpdate() { - this.getBlockchainData(); - if (this.state.readyForAnimation) { - if (this.postRef) { - setTimeout(() => { - this.postRef.current.scrollIntoView( - { - block: 'start', behavior: 'smooth' - }, - ); - setTimeout(() => { - this.setState({ - animateOnToggle: false - }); - }, 300); - }, 100); - this.setState({ - readyForAnimation: false - }); - } - } - } } +Post.propTypes = { + getFocus: PropTypes.bool.isRequired, + user: PropTypes.object.isRequired, + orbitDB: PropTypes.object.isRequired, + avatarUrl: PropTypes.string, + postIndex: PropTypes.number.isRequired, + navigateTo: PropTypes.func.isRequired, + postData: PropTypes.object, + postID: PropTypes.string.isRequired +}; + const mapDispatchToProps = dispatch => bindActionCreators({ navigateTo: location => push(location) }, dispatch); diff --git a/app/src/components/PostList.js b/app/src/components/PostList.js index ea449fb..22a1024 100644 --- a/app/src/components/PostList.js +++ b/app/src/components/PostList.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { drizzle } from '../index'; @@ -18,31 +19,6 @@ class PostList extends Component { }; } - render() { - const posts = this.props.postIDs.map((postID, index) => ( - - )); - - return ( -
- {this.props.recentToTheTop - ? posts.slice(0).reverse() - : posts - } -
- ); - } - componentDidMount() { this.getBlockchainData(); } @@ -52,12 +28,15 @@ class PostList extends Component { } getBlockchainData() { - if (this.props.drizzleStatus.initialized) { - const dataKeysShallowCopy = this.state.dataKeys.slice(); + const { dataKeys } = this.state; + const { drizzleStatus, postIDs } = this.props; + + if (drizzleStatus.initialized) { + const dataKeysShallowCopy = dataKeys.slice(); let fetchingNewData = false; - this.props.postIDs.forEach((postID) => { - if (!this.state.dataKeys[postID]) { + postIDs.forEach((postID) => { + if (!dataKeys[postID]) { dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall( postID, ); @@ -72,8 +51,44 @@ class PostList extends Component { } } } + + render() { + const { dataKeys } = this.state; + const { postIDs, contracts, focusOnPost, recentToTheTop } = this.props; + + const posts = postIDs.map((postID, index) => ( + + )); + + return ( +
+ {recentToTheTop + ? posts.slice(0).reverse() + : posts + } +
+ ); + } } +PostList.propTypes = { + drizzleStatus: PropTypes.object.isRequired, + postIDs: PropTypes.array.isRequired, + contracts: PropTypes.array.isRequired, + focusOnPost: PropTypes.number, + recentToTheTop: PropTypes.bool +}; + const mapStateToProps = state => ({ contracts: state.contracts, drizzleStatus: state.drizzleStatus diff --git a/app/src/components/ProfileInformation.js b/app/src/components/ProfileInformation.js index 9fd71d5..512696f 100644 --- a/app/src/components/ProfileInformation.js +++ b/app/src/components/ProfileInformation.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import UserAvatar from 'react-user-avatar'; import { drizzle } from '../index'; @@ -30,12 +31,23 @@ class ProfileInformation extends Component { }; } + componentDidMount() { + this.getBlockchainData(); + } + + componentDidUpdate() { + this.getBlockchainData(); + } + getBlockchainData() { - if (this.state.pageStatus === 'initialized' - && this.props.drizzleStatus.initialized) { + const { pageStatus, dateOfRegister, orbitDBId } = this.state; + const { drizzleStatus, address, contracts } = this.props; + + if (pageStatus === 'initialized' + && drizzleStatus.initialized) { callsInfo.forEach((call, index) => { this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall( - this.props.address, + address, ); }); this.setState({ @@ -43,32 +55,32 @@ class ProfileInformation extends Component { }); } - if (this.state.pageStatus === 'loading') { - let pageStatus = 'loaded'; + if (pageStatus === 'loading') { + let pageStatusUpdate = 'loaded'; callsInfo.forEach((call, index) => { - if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) { - pageStatus = 'loading'; + if (!contracts[call.contract][call.method][this.dataKey[index]]) { + pageStatusUpdate = 'loading'; } }); - if (pageStatus === 'loaded') { + if (pageStatusUpdate === 'loaded') { this.setState({ - pageStatus + pageStatus: pageStatusUpdate }); } } - if (this.state.pageStatus === 'loaded') { - if (this.state.dateOfRegister === '') { - const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; + if (pageStatus === 'loaded') { + if (dateOfRegister === '') { + const transaction = contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; if (transaction) { this.setState({ dateOfRegister: transaction.value }); } } - if (this.state.orbitDBId === '') { - const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; + if (orbitDBId === '') { + const transaction = contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; if (transaction) { this.setState({ orbitDBId: transaction.value @@ -79,62 +91,68 @@ class ProfileInformation extends Component { } render() { + const { orbitDBId, dateOfRegister } = this.state; + const { avatarUrl, username, address, numberOfTopics, numberOfPosts, self } = this.props; + return (
- {this.props.avatarUrl && ( + {avatarUrl && ( )} - + - + - + - + - + - {this.state.dateOfRegister + {dateOfRegister && ( - + ) }
Username:{this.props.username}{username}
Account address:{this.props.address}{address}
OrbitDB:{this.state.orbitDBId}{orbitDBId}
Number of topics created:{this.props.numberOfTopics}{numberOfTopics}
Number of posts:{this.props.numberOfPosts}{numberOfPosts}
Member since:{epochTimeConverter(this.state.dateOfRegister)}{epochTimeConverter(dateOfRegister)}
- {this.props.self && } + {self && }
); } - - componentDidMount() { - this.getBlockchainData(); - } - - componentDidUpdate() { - this.getBlockchainData(); - } } +ProfileInformation.propTypes = { + drizzleStatus: PropTypes.object.isRequired, + contracts: PropTypes.array.isRequired, + avatarUrl: PropTypes.string, + username: PropTypes.string.isRequired, + address: PropTypes.string.isRequired, + numberOfTopics: PropTypes.number.isRequired, + numberOfPosts: PropTypes.number.isRequired, + self: PropTypes.bool +}; + const mapStateToProps = state => ({ drizzleStatus: state.drizzleStatus, contracts: state.contracts, diff --git a/app/src/components/Topic.js b/app/src/components/Topic.js index e2d26e4..e48bcb2 100644 --- a/app/src/components/Topic.js +++ b/app/src/components/Topic.js @@ -1,4 +1,6 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { GetTopicResult } from '../CustomPropTypes' import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; @@ -20,16 +22,41 @@ class Topic extends Component { }; } + componentDidMount() { + const { topicSubjectFetchStatus } = this.state; + const { topicData, orbitDB, topicID } = this.props; + + if (topicData !== null + && topicSubjectFetchStatus === 'pending' + && orbitDB.ipfsInitialized + && orbitDB.orbitdb) { + this.fetchSubject(topicID); + } + } + + componentDidUpdate() { + const { topicSubjectFetchStatus } = this.state; + const { topicData, orbitDB, topicID } = this.props; + + if (topicData !== null + && topicSubjectFetchStatus === 'pending' + && orbitDB.ipfsInitialized + && orbitDB.orbitdb) { + this.fetchSubject(topicID); + } + } + async fetchSubject(topicID) { + const { topicData, user, orbitDB } = this.props; let topicSubject; - if (this.props.topicData.value[1] === this.props.user.address) { - const orbitData = this.props.orbitDB.topicsDB.get(topicID); + if (topicData.value[1] === user.address) { + const orbitData = orbitDB.topicsDB.get(topicID); topicSubject = orbitData.subject; } else { - const fullAddress = `/orbitdb/${this.props.topicData.value[0] + const fullAddress = `/orbitdb/${topicData.value[0] }/topics`; - const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); + const store = await orbitDB.orbitdb.keyvalue(fullAddress); await store.load(); const localOrbitData = store.get(topicID); @@ -50,21 +77,24 @@ class Topic extends Component { } render() { + const { topicSubject } = this.state; + const { history, topicID, topicData } = this.props; + return ( { - this.props.history.push(`/topic/${this.props.topicID}`); + history.push(`/topic/${topicID}`); }} >

- {this.state.topicSubject !== null ? this.state.topicSubject + {topicSubject !== null ? topicSubject : (

- {this.props.topicData !== null - ? this.props.topicData.value[2] + {topicData !== null + ? topicData.value[2] : 'Username' }

- {`Number of replies: ${this.props.topicData !== null - ? this.props.topicData.value[4].length + {`Number of replies: ${topicData !== null + ? topicData.value[4].length : ''}` }

- {this.props.topicData !== null + {topicData !== null && ( ) } @@ -111,26 +141,16 @@ class Topic extends Component { ); } - - componentDidMount() { - if (this.props.topicData !== null - && this.state.topicSubjectFetchStatus === 'pending' - && this.props.orbitDB.ipfsInitialized - && this.props.orbitDB.orbitdb) { - this.fetchSubject(this.props.topicID); - } - } - - componentDidUpdate() { - if (this.props.topicData !== null - && this.state.topicSubjectFetchStatus === 'pending' - && this.props.orbitDB.ipfsInitialized - && this.props.orbitDB.orbitdb) { - this.fetchSubject(this.props.topicID); - } - } } +Topic.propTypes = { + user: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, + topicData: GetTopicResult.isRequired, + orbitDB: PropTypes.object.isRequired, + topicID: PropTypes.string.isRequired +}; + const mapStateToProps = state => ({ user: state.user, orbitDB: state.orbit diff --git a/app/src/components/TopicList.js b/app/src/components/TopicList.js index d0b599f..dd00318 100644 --- a/app/src/components/TopicList.js +++ b/app/src/components/TopicList.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { drizzle } from '../index'; @@ -18,16 +19,26 @@ class TopicList extends Component { }; } + componentDidMount() { + this.getBlockchainData(); + } + + componentDidUpdate() { + this.getBlockchainData(); + } + getBlockchainData() { - if (this.props.drizzleStatus.initialized) { - const dataKeysShallowCopy = this.state.dataKeys.slice(); + const { dataKeys } = this.state; + const { drizzleStatus, topicIDs } = this.props; + + if (drizzleStatus.initialized) { + const dataKeysShallowCopy = dataKeys.slice(); let fetchingNewData = false; - this.props.topicIDs.forEach((topicID) => { - if (!this.state.dataKeys[topicID]) { - dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall( - topicID, - ); + topicIDs.forEach((topicID) => { + if (!dataKeys[topicID]) { + dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod] + .cacheCall(topicID); fetchingNewData = true; } }); @@ -41,11 +52,14 @@ class TopicList extends Component { } render() { - const topics = this.props.topicIDs.map(topicID => ( + const { dataKeys } = this.state; + const { topicIDs, contracts } = this.props; + + const topics = topicIDs.map(topicID => ( ); } - - componentDidMount() { - this.getBlockchainData(); - } - - componentDidUpdate() { - this.getBlockchainData(); - } } +TopicList.propTypes = { + topicIDs: PropTypes.arrayOf(PropTypes.string).isRequired, + contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired, + drizzleStatus: PropTypes.object.isRequired +}; + const mapStateToProps = state => ({ contracts: state.contracts, drizzleStatus: state.drizzleStatus diff --git a/app/src/containers/BoardContainer.js b/app/src/containers/BoardContainer.js index 2e0d41c..49b1395 100644 --- a/app/src/containers/BoardContainer.js +++ b/app/src/containers/BoardContainer.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; @@ -27,16 +28,27 @@ class BoardContainer extends Component { }; } + componentDidMount() { + this.getBlockchainData(); + } + + componentDidUpdate() { + this.getBlockchainData(); + } + getBlockchainData() { - if (this.state.pageStatus === 'initialized' - && this.props.drizzleStatus.initialized) { + const { pageStatus } = this.state; + const { drizzleStatus, contracts } = this.props; + + if (pageStatus === 'initialized' + && drizzleStatus.initialized) { this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall(); this.setState({ pageStatus: 'loading' }); } - if (this.state.pageStatus === 'loading' - && this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]) { + if (pageStatus === 'loading' + && contracts[contract][getNumberOfTopicsMethod][this.dataKey]) { this.setState({ pageStatus: 'loaded' }); @@ -45,13 +57,17 @@ class BoardContainer extends Component { } handleCreateTopicClick() { - this.props.history.push('/startTopic'); + const { history } = this.props; + history.push('/startTopic'); } render() { + const { pageStatus } = this.state; + const { contracts, hasSignedUp } = this.props; + let boardContents; - if (this.state.pageStatus === 'loaded') { - const numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; + if (pageStatus === 'loaded') { + const numberOfTopics = contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; if (numberOfTopics !== '0') { this.topicIDs = []; @@ -61,7 +77,7 @@ class BoardContainer extends Component { boardContents = ([ ,

, - this.props.hasSignedUp + hasSignedUp && ( ) ]); - } else if (!this.props.hasSignedUp) { + } else if (!hasSignedUp) { boardContents = (
@@ -105,16 +121,15 @@ class BoardContainer extends Component {
); } - - componentDidMount() { - this.getBlockchainData(); - } - - componentDidUpdate() { - this.getBlockchainData(); - } } +BoardContainer.propTypes = { + drizzleStatus: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, + contracts: PropTypes.objectOf(PropTypes.object).isRequired, + hasSignedUp: PropTypes.bool.isRequired +}; + const mapStateToProps = state => ({ contracts: state.contracts, drizzleStatus: state.drizzleStatus, diff --git a/app/src/containers/CoreLayoutContainer.js b/app/src/containers/CoreLayoutContainer.js index ab1d96f..4c1a6c9 100644 --- a/app/src/containers/CoreLayoutContainer.js +++ b/app/src/containers/CoreLayoutContainer.js @@ -1,4 +1,5 @@ -import React, { Component } from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import NavBarContainer from './NavBarContainer'; import RightSideBarContainer from './TransactionsMonitorContainer'; @@ -14,31 +15,31 @@ import '../assets/css/profile-container.css'; /* import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer'; */ -class CoreLayout extends Component { - render() { - return ( -
- - {/*
-
-
-
-
*/} -
-
); } - - componentDidMount() { - this.getBlockchainData(); - } - - componentDidUpdate() { - this.getBlockchainData(); - } - - componentWillUnmount() { - this.props.setNavBarTitle(''); - } } +ProfileContainer.propTypes = { + match: PropTypes.object.isRequired, + drizzleStatus: PropTypes.object.isRequired, + contracts: PropTypes.array.isRequired, + navigateTo: PropTypes.func.isRequired, + user: PropTypes.object.isRequired, + setNavBarTitle: PropTypes.func.isRequired +}; + const mapDispatchToProps = dispatch => bindActionCreators({ navigateTo: location => push(location), setNavBarTitle: navBarTitle => setNavBarTitle(navBarTitle) diff --git a/app/src/containers/SignUpContainer.js b/app/src/containers/SignUpContainer.js index 0c0013f..eebad66 100644 --- a/app/src/containers/SignUpContainer.js +++ b/app/src/containers/SignUpContainer.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { Header } from 'semantic-ui-react'; import { connect } from 'react-redux'; @@ -6,12 +7,15 @@ import UsernameFormContainer from './UsernameFormContainer'; class SignUpContainer extends Component { componentDidUpdate(prevProps) { - if (this.props.user.hasSignedUp && !prevProps.user.hasSignedUp) this.props.history.push('/'); + const { user, history } = this.props; + if (user.hasSignedUp && !prevProps.user.hasSignedUp) history.push('/'); } render() { + const { user } = this.props; + return ( - this.props.user.hasSignedUp + user.hasSignedUp ? (
@@ -29,7 +33,7 @@ class SignUpContainer extends Component {

Account address: {' '} - {this.props.user.address} + {user.address}

@@ -39,6 +43,11 @@ class SignUpContainer extends Component { } } +SignUpContainer.propTypes = { + user: PropTypes.object.isRequired, + history: PropTypes.object.isRequired +}; + const mapStateToProps = state => ({ user: state.user }); diff --git a/app/src/containers/StartTopicContainer.js b/app/src/containers/StartTopicContainer.js index a3f978a..195e410 100644 --- a/app/src/containers/StartTopicContainer.js +++ b/app/src/containers/StartTopicContainer.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Button, Form, Icon, TextArea } from 'semantic-ui-react'; @@ -7,7 +8,7 @@ import NewTopicPreview from '../components/NewTopicPreview'; import { createTopic } from '../redux/actions/transactionsActions'; class StartTopicContainer extends Component { - constructor(props, context) { + constructor(props) { super(props); this.handleInputChange = this.handleInputChange.bind(this); @@ -25,24 +26,27 @@ class StartTopicContainer extends Component { } async validateAndPost() { - if (this.state.topicSubjectInput === '' || this.state.topicMessageInput + const { topicSubjectInput, topicMessageInput } = this.state; + const { dispatch, history } = this.props; + + if (topicSubjectInput === '' || topicMessageInput === '') { this.setState({ - topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', - topicMessageInputEmptySubmit: this.state.topicMessageInput === '' + topicSubjectInputEmptySubmit: topicSubjectInput === '', + topicMessageInputEmptySubmit: topicMessageInput === '' }); return; } - this.props.dispatch( + dispatch( createTopic( { - topicSubject: this.state.topicSubjectInput, - topicMessage: this.state.topicMessageInput + topicSubject: topicSubjectInput, + topicMessage: topicMessageInput }, ), ); - this.props.history.push('/home'); + history.push('/home'); } handleInputChange(event) { @@ -52,7 +56,7 @@ class StartTopicContainer extends Component { } handlePreviewToggle() { - this.setState((prevState, props) => ({ + this.setState((prevState) => ({ previewEnabled: !prevState.previewEnabled, previewDate: this.getDate() })); @@ -69,32 +73,38 @@ class StartTopicContainer extends Component { } render() { - if (!this.props.user.hasSignedUp) { - this.props.history.push('/signup'); + const { + previewDate, previewEnabled, topicSubjectInputEmptySubmit, topicSubjectInput, + topicMessageInputEmptySubmit, topicMessageInput + } = this.state; + const { user, history } = this.props; + + if (!user.hasSignedUp) { + history.push('/signup'); return (null); } - const previewEditText = this.state.previewEnabled ? 'Edit' : 'Preview'; + const previewEditText = previewEnabled ? 'Edit' : 'Preview'; return (
- {this.state.previewEnabled + {previewEnabled && ( ) }
- {!this.state.previewEnabled + {!previewEnabled && [ ({ orbitDB: state.orbitDB, user: state.user diff --git a/app/src/containers/TopicContainer.js b/app/src/containers/TopicContainer.js index a317eb1..29c0fe9 100644 --- a/app/src/containers/TopicContainer.js +++ b/app/src/containers/TopicContainer.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { push } from 'connected-react-router'; import { connect } from 'react-redux'; @@ -17,9 +18,11 @@ class TopicContainer extends Component { constructor(props) { super(props); + const { match, navigateTo } = props; + // Topic ID should be a positive integer - if (!/^[0-9]+$/.test(this.props.match.params.topicId)) { - this.props.navigateTo('/404'); + if (!/^[0-9]+$/.test(match.params.topicId)) { + navigateTo('/404'); } this.getBlockchainData = this.getBlockchainData.bind(this); @@ -29,46 +32,62 @@ class TopicContainer extends Component { this.state = { pageStatus: 'initialized', - topicID: parseInt(this.props.match.params.topicId), + topicID: parseInt(match.params.topicId), topicSubject: null, - postFocus: this.props.match.params.postId - && /^[0-9]+$/.test(this.props.match.params.postId) - ? this.props.match.params.postId + postFocus: match.params.postId + && /^[0-9]+$/.test(match.params.postId) + ? match.params.postId : null, fetchTopicSubjectStatus: 'pending', posting: false }; } + componentDidMount() { + this.getBlockchainData(); + } + + componentDidUpdate() { + this.getBlockchainData(); + } + + componentWillUnmount() { + const { setNavBarTitle } = this.props; + setNavBarTitle(''); + } + getBlockchainData() { - if (this.state.pageStatus === 'initialized' - && this.props.drizzleStatus.initialized) { + const { pageStatus, topicID, fetchTopicSubjectStatus } = this.state; + const { drizzleStatus, orbitDB, contracts } = this.props; + + if (pageStatus === 'initialized' + && drizzleStatus.initialized) { this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall( - this.state.topicID, + topicID, ); this.setState({ pageStatus: 'loading' }); } - if (this.state.pageStatus === 'loading' - && this.props.contracts[contract][getTopicMethod][this.dataKey]) { + if (pageStatus === 'loading' + && contracts[contract][getTopicMethod][this.dataKey]) { this.setState({ pageStatus: 'loaded' }); - if (this.props.orbitDB.orbitdb !== null) { + if (orbitDB.orbitdb !== null) { this.fetchTopicSubject( - this.props.contracts[contract][getTopicMethod][this.dataKey].value[0], + contracts[contract][getTopicMethod][this.dataKey].value[0], ); this.setState({ fetchTopicSubjectStatus: 'fetching' }); } } - if (this.state.pageStatus === 'loaded' - && this.state.fetchTopicSubjectStatus === 'pending' - && this.props.orbitDB.orbitdb !== null) { + if (pageStatus === 'loaded' + && fetchTopicSubjectStatus === 'pending' + && orbitDB.orbitdb !== null) { this.fetchTopicSubject( - this.props.contracts[contract][getTopicMethod][this.dataKey].value[0], + contracts[contract][getTopicMethod][this.dataKey].value[0], ); this.setState({ fetchTopicSubjectStatus: 'fetching' @@ -77,27 +96,30 @@ class TopicContainer extends Component { } async fetchTopicSubject(orbitDBAddress) { + const { topicID } = this.state; + const { contracts, user, orbitDB, setNavBarTitle } = this.props; + let orbitData; - if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1] - === this.props.user.address) { - orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID); + if (contracts[contract][getTopicMethod][this.dataKey].value[1] + === user.address) { + orbitData = orbitDB.topicsDB.get(topicID); } else { const fullAddress = `/orbitdb/${orbitDBAddress}/topics`; - const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); + const store = await orbitDB.orbitdb.keyvalue(fullAddress); await store.load(); - const localOrbitData = store.get(this.state.topicID); + const localOrbitData = store.get(topicID); if (localOrbitData) { orbitData = localOrbitData; } else { // Wait until we have received something from the network store.events.on('replicated', () => { - orbitData = store.get(this.state.topicID); + orbitData = store.get(topicID); }); } } - this.props.setNavBarTitle(orbitData.subject); + setNavBarTitle(orbitData.subject); this.setState({ topicSubject: orbitData.subject, fetchTopicSubjectStatus: 'fetched' @@ -120,30 +142,33 @@ class TopicContainer extends Component { } render() { + const { pageStatus, postFocus, topicID, topicSubject, posting } = this.state; + const { contracts, user } = this.props; + let topicContents; - if (this.state.pageStatus === 'loaded') { + if (pageStatus === 'loaded') { topicContents = ( (
- {this.state.posting + {posting && ( { this.togglePostingState(); }} onPostCreated={() => { this.postCreated(); }} /> ) }
- {this.props.user.hasSignedUp && !this.state.posting + {user.hasSignedUp && !posting && }
@@ -154,26 +179,24 @@ class TopicContainer extends Component { return (
{topicContents} - {!this.state.posting + {!posting &&
}
); } - - componentDidMount() { - this.getBlockchainData(); - } - - componentDidUpdate() { - this.getBlockchainData(); - } - - componentWillUnmount() { - this.props.setNavBarTitle(''); - } } +TopicContainer.propTypes = { + drizzleStatus: PropTypes.object.isRequired, + orbitDB: PropTypes.object.isRequired, + setNavBarTitle: PropTypes.func.isRequired, + contracts: PropTypes.array.isRequired, + user: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, + navigateTo: PropTypes.func.isRequired +}; + const mapDispatchToProps = dispatch => bindActionCreators({ navigateTo: location => push(location), setNavBarTitle: navBarTitle => setNavBarTitle(navBarTitle) diff --git a/app/src/containers/TransactionsMonitorContainer.js b/app/src/containers/TransactionsMonitorContainer.js index 55739b7..0021910 100644 --- a/app/src/containers/TransactionsMonitorContainer.js +++ b/app/src/containers/TransactionsMonitorContainer.js @@ -1,11 +1,12 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { Message } from 'semantic-ui-react'; class RightSideBar extends Component { - constructor(props, context) { + constructor(props) { super(props); this.handleMessageClick = this.handleMessageClick.bind(this); @@ -17,33 +18,35 @@ class RightSideBar extends Component { } handleMessageClick(index) { - const transactionHash = this.props.transactionStack[index]; - if (this.props.transactions[transactionHash]) { - if (this.props.transactions[transactionHash].status === 'error') { + const { transactionStack, history, transactions } = this.props; + + const transactionHash = transactionStack[index]; + if (transactions[transactionHash]) { + if (transactions[transactionHash].status === 'error') { this.handleMessageDismiss(null, index); - } else if (this.props.transactions[transactionHash].receipt - && this.props.transactions[transactionHash].receipt.events) { + } else if (transactions[transactionHash].receipt + && transactions[transactionHash].receipt.events) { switch (Object.keys( - this.props.transactions[transactionHash].receipt.events, + transactions[transactionHash].receipt.events, )[0]) { case 'UserSignedUp': - this.props.history.push('/profile'); + history.push('/profile'); this.handleMessageDismiss(null, index); break; case 'UsernameUpdated': - this.props.history.push('/profile'); + history.push('/profile'); this.handleMessageDismiss(null, index); break; case 'TopicCreated': - this.props.history.push(`/topic/${ - this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID}`); + history.push(`/topic/${ + transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID}`); this.handleMessageDismiss(null, index); break; case 'PostCreated': - this.props.history.push(`/topic/${ - this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID + history.push(`/topic/${ + transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID }/${ - this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID}`); + transactions[transactionHash].receipt.events.PostCreated.returnValues.postID}`); this.handleMessageDismiss(null, index); break; default: @@ -59,7 +62,9 @@ class RightSideBar extends Component { event.stopPropagation(); } - const isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice(); + const { isTransactionMessageDismissed } = this.state; + + const isTransactionMessageDismissedShallowCopy = isTransactionMessageDismissed.slice(); isTransactionMessageDismissedShallowCopy[messageIndex] = true; this.setState({ isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy @@ -67,13 +72,16 @@ class RightSideBar extends Component { } render() { - if (this.props.transactionStack.length === 0) { + const { isTransactionMessageDismissed } = this.state; + const { transactionStack, transactions } = this.props; + + if (transactionStack.length === 0) { return null; } - const transactionMessages = this.props.transactionStack.map( + const transactionMessages = transactionStack.map( (transaction, index) => { - if (this.state.isTransactionMessageDismissed[index]) { + if (isTransactionMessageDismissed[index]) { return null; } @@ -82,20 +90,20 @@ class RightSideBar extends Component { message.push( 'New transaction has been queued and is waiting your confirmation.', ); - if (this.props.transactions[transaction]) { + if (transactions[transaction]) { message.push(
); message.push('- transaction confirmed'); } - if (this.props.transactions[transaction] - && this.props.transactions[transaction].status === 'success') { + if (transactions[transaction] + && transactions[transaction].status === 'success') { /* Transaction completed successfully */ message.push(
); message.push('- transaction mined'); color = 'green'; message.push(
); message.push('- transaction completed successfully'); - } else if (this.props.transactions[transaction] - && this.props.transactions[transaction].status === 'error') { + } else if (transactions[transaction] + && transactions[transaction].status === 'error') { /* Transaction failed to complete */ message.push(
); message.push('- transaction mined'); @@ -127,6 +135,12 @@ class RightSideBar extends Component { } } +RightSideBar.propTypes = { + transactionStack: PropTypes.array.isRequired, + history: PropTypes.object.isRequired, + transactions: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired +}; + const mapStateToProps = state => ({ transactions: state.transactions, transactionStack: state.transactionStack diff --git a/app/src/containers/UsernameFormContainer.js b/app/src/containers/UsernameFormContainer.js index a965557..4e8be75 100644 --- a/app/src/containers/UsernameFormContainer.js +++ b/app/src/containers/UsernameFormContainer.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Button, Dimmer, Form, Header, Loader, Message } from 'semantic-ui-react'; @@ -48,16 +49,18 @@ class UsernameFormContainer extends Component { } handleSubmit() { - if (this.state.usernameInput === '') { + const { usernameInput, error } = this.state; + + if (usernameInput === '') { this.setState({ error: true, errorHeader: 'Data Incomplete', errorMessage: 'You need to provide a username' }); - } else if (!this.state.error) { + } else if (!error) { // Makes sure current input username has been checked for availability if (this.checkedUsernames.some( - e => e.usernameChecked === this.state.usernameInput, + e => e.usernameChecked === usernameInput, )) { this.completeAction(); } @@ -65,8 +68,11 @@ class UsernameFormContainer extends Component { } async completeAction() { - if (this.props.user.hasSignedUp) { - this.props.dispatch(updateUsername(...[this.state.usernameInput], null)); + const { usernameInput } = this.state; + const { user, dispatch, account } = this.props; + + if (user.hasSignedUp) { + dispatch(updateUsername(...[usernameInput], null)); } else { this.setState({ signingUp: true @@ -74,7 +80,7 @@ class UsernameFormContainer extends Component { const orbitdbInfo = await createDatabases(); this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend( ...[ - this.state.usernameInput, + usernameInput, orbitdbInfo.identityId, orbitdbInfo.identityPublicKey, orbitdbInfo.identityPrivateKey, @@ -84,7 +90,7 @@ class UsernameFormContainer extends Component { orbitdbInfo.topicsDB, orbitdbInfo.postsDB ], { - from: this.props.account + from: account }, ); } @@ -94,18 +100,21 @@ class UsernameFormContainer extends Component { } componentDidUpdate() { - if (this.state.signingUp) { - const txHash = this.props.transactionStack[this.stackId]; + const { signingUp, usernameInput, error } = this.state; + const { transactionStack, transactions, contracts } = this.props; + + if (signingUp) { + const txHash = transactionStack[this.stackId]; if (txHash - && this.props.transactions[txHash] - && this.props.transactions[txHash].status === 'error') { + && transactions[txHash] + && transactions[txHash].status === 'error') { this.setState({ signingUp: false }); } } else { const temp = Object.values( - this.props.contracts[contract][checkUsernameTakenMethod], + contracts[contract][checkUsernameTakenMethod], ); this.checkedUsernames = temp.map(checked => ({ usernameChecked: checked.args[0], @@ -114,8 +123,8 @@ class UsernameFormContainer extends Component { if (this.checkedUsernames.length > 0) { this.checkedUsernames.forEach((checked) => { - if (checked.usernameChecked === this.state.usernameInput - && checked.isTaken && !this.state.error) { + if (checked.usernameChecked === usernameInput + && checked.isTaken && !error) { this.setState({ error: true, errorHeader: 'Data disapproved', @@ -128,14 +137,15 @@ class UsernameFormContainer extends Component { } render() { - const { hasSignedUp } = this.props.user; + const { error, usernameInput, errorHeader, errorMessage, signingUp } = this.state; + const { user } = this.props; - if (hasSignedUp !== null) { - const buttonText = hasSignedUp ? 'Update' : 'Sign Up'; - const placeholderText = hasSignedUp - ? this.props.user.username + if (user.hasSignedUp !== null) { + const buttonText = user.hasSignedUp ? 'Update' : 'Sign Up'; + const placeholderText = user.hasSignedUp + ? user.username : 'Username'; - const withError = this.state.error && { + const withError = error && { error: true }; @@ -158,18 +168,18 @@ class UsernameFormContainer extends Component { - +
Magic elves are processing your noble @@ -185,6 +195,16 @@ Magic elves are processing your noble } } +UsernameFormContainer.propTypes = { + dispatch: PropTypes.func.isRequired, + account: PropTypes.string.isRequired, + transactionStack: PropTypes.array.isRequired, + transactions: PropTypes.array.isRequired, + contracts: PropTypes.array.isRequired, + hasSignedUp: PropTypes.object.isRequired, + user: PropTypes.object.isRequired +}; + const mapStateToProps = state => ({ account: state.accounts[0], contracts: state.contracts, diff --git a/app/src/redux/sagas/transactionsSaga.js b/app/src/redux/sagas/transactionsSaga.js index ef81c97..22fa0cc 100644 --- a/app/src/redux/sagas/transactionsSaga.js +++ b/app/src/redux/sagas/transactionsSaga.js @@ -7,7 +7,8 @@ import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; const transactionsHistory = Object.create(null); function* initTransaction(action) { - const dataKey = drizzle.contracts[action.transactionDescriptor.contract].methods[action.transactionDescriptor.method].cacheSend( + const dataKey = drizzle.contracts[action.transactionDescriptor.contract] + .methods[action.transactionDescriptor.method].cacheSend( ...(action.transactionDescriptor.params), ); diff --git a/package.json b/package.json index c31bb8c..3f39bb1 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "openzeppelin-solidity": "^2.1.2" }, "devDependencies": { - "eslint": "5.15.1", + "eslint": "5.12.0", "eslint-config-airbnb": "17.1.0", "eslint-plugin-import": "2.16.0", "eslint-plugin-jsx-a11y": "6.2.1",