Browse Source

Fix lint errors

develop
Apostolos Fanakis 6 years ago
parent
commit
597a7c52d3
  1. 11
      app/src/CustomPropTypes.js
  2. 9
      app/src/components/FloatingButton.js
  3. 12
      app/src/components/LoadingSpinner.js
  4. 108
      app/src/components/NewPost.js
  5. 106
      app/src/components/NewTopicPreview.js
  6. 137
      app/src/components/Post.js
  7. 73
      app/src/components/PostList.js
  8. 84
      app/src/components/ProfileInformation.js
  9. 86
      app/src/components/Topic.js
  10. 50
      app/src/components/TopicList.js
  11. 49
      app/src/containers/BoardContainer.js
  12. 51
      app/src/containers/CoreLayoutContainer.js
  13. 91
      app/src/containers/NavBarContainer.js
  14. 106
      app/src/containers/ProfileContainer.js
  15. 15
      app/src/containers/SignUpContainer.js
  16. 58
      app/src/containers/StartTopicContainer.js
  17. 115
      app/src/containers/TopicContainer.js
  18. 60
      app/src/containers/TransactionsMonitorContainer.js
  19. 68
      app/src/containers/UsernameFormContainer.js
  20. 3
      app/src/redux/sagas/transactionsSaga.js
  21. 2
      package.json

11
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 };

9
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 => (
<div className="action-button" onClick={props.onClick}>
const FloatingButton = ({ onClick }) => (
<div className="action-button" role="button" onClick={onClick} onKeyUp={onClick} tabIndex={0}>
<Button icon color="teal" size="large">
<Icon name="add" />
</Button>
</div>
);
FloatingButton.propTypes = {
onClick: PropTypes.func.isRequired
};
export default FloatingButton;

12
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 }) => (
<div className="vertical-center-children">
<div
className={`center-in-parent ${
props.className ? props.className : ''}`}
style={props.style ? props.style : []}
className ? className : ''}`}
style={style ? style : []}
>
<p>
<i className="fas fa-spinner fa-3x fa-spin" />
@ -14,4 +15,9 @@ const LoadingSpinner = props => (
</div>
);
LoadingSpinner.propTypes = {
className: PropTypes.string,
style: PropTypes.string
};
export default LoadingSpinner;

108
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 (
<div className="post" ref={this.newPostOuterRef}>
<Divider horizontal>
<span className="grey-text">
#
{this.props.postIndex}
{postIndex}
</span>
</Divider>
<Grid>
@ -87,39 +102,39 @@ class NewPost extends Component {
<UserAvatar
size="52"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.user.username}
src={avatarUrl}
name={user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span><strong>{this.props.user.username}</strong></span>
<span><strong>{user.username}</strong></span>
<span className="grey-text">
{this.state.previewEnabled
&& <TimeAgo date={this.state.previewDate} />
{previewEnabled
&& <TimeAgo date={previewDate} />
}
</span>
</div>
<div className="stretch-space-between">
<span>
<strong>
{this.state.previewEnabled
{previewEnabled
&& (`Subject: ${
this.state.postSubjectInput}`)
postSubjectInput}`)
}
</strong>
</span>
</div>
<div className="post-content">
<div style={{
display: this.state.previewEnabled
display: previewEnabled
? 'block'
: 'none'
}}
>
<ReactMarkdown
source={this.state.postContentInput}
source={postContentInput}
className="markdown-preview"
/>
</div>
@ -127,14 +142,14 @@ class NewPost extends Component {
<Form.Input
key="postSubjectInput"
style={{
display: this.state.previewEnabled
display: previewEnabled
? 'none'
: ''
}}
name="postSubjectInput"
error={this.state.postSubjectInputEmptySubmit}
error={postSubjectInputEmptySubmit}
type="text"
value={this.state.postSubjectInput}
value={postSubjectInput}
placeholder="Subject"
id="postSubjectInput"
onChange={this.handleInputChange}
@ -142,15 +157,15 @@ class NewPost extends Component {
<TextArea
key="postContentInput"
style={{
display: this.state.previewEnabled
display: previewEnabled
? 'none'
: ''
}}
name="postContentInput"
className={this.state.postContentInputEmptySubmit
className={postContentInputEmptySubmit
? 'form-textarea-required'
: ''}
value={this.state.postContentInput}
value={postContentInput}
placeholder="Post"
id="postContentInput"
onChange={this.handleInputChange}
@ -177,11 +192,11 @@ class NewPost extends Component {
onClick={this.handlePreviewToggle}
color="yellow"
>
{this.state.previewEnabled ? 'Edit' : 'Preview'}
{previewEnabled ? 'Edit' : 'Preview'}
</Button>
<Button
type="button"
onClick={this.props.onCancelClick}
onClick={onCancelClick}
color="red"
>
Cancel
@ -196,12 +211,19 @@ class NewPost extends Component {
</div>
);
}
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

106
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 }) => (
<div className="post">
<Divider horizontal>
<span className="grey-text">#0</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<UserAvatar
size="52"
className="inline"
src={user.avatarUrl}
name={user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span>
<strong>
{user.username}
</strong>
</span>
<span className="grey-text">
<TimeAgo date={date} />
</span>
</div>
<div className="stretch-space-between">
<span>
<strong>
Subject:
{' '}
{subject}
</strong>
</span>
</div>
<div className="post-content">
<ReactMarkdown source={content} />
</div>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
render() {
return (
<div className="post">
<Divider horizontal>
<span className="grey-text">#0</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<UserAvatar
size="52"
className="inline"
src={this.props.user.avatarUrl}
name={this.props.user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span>
<strong>
{this.props.user.username}
</strong>
</span>
<span className="grey-text">
<TimeAgo date={this.props.date} />
</span>
</div>
<div className="stretch-space-between">
<span>
<strong>
Subject:
{' '}
{this.props.subject}
</strong>
</span>
</div>
<div className="post-content">
<ReactMarkdown source={this.props.content} />
</div>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
}
}
Post.propTypes = {
subject: PropTypes.string,
date: PropTypes.string,
content: PropTypes.string,
user: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
user: state.user

137
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
? (
<UserAvatar
size="52"
className="inline"
src={this.props.avatarUrl}
name={this.props.postData.value[2]}
src={avatarUrl}
name={postData.value[2]}
/>
)
: (
@ -99,23 +136,23 @@ class Post extends Component {
<Transition
animation="tada"
duration={500}
visible={this.state.animateOnToggle}
visible={animateOnToggle}
>
<div className="post" ref={this.postRef ? this.postRef : null}>
<Divider horizontal>
<span className="grey-text">
#
{this.props.postIndex}
{postIndex}
</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
{this.props.postData !== null
{postData !== null
? (
<Link
to={`/profile/${this.props.postData.value[1]
}/${this.props.postData.value[2]}`}
to={`/profile/${postData.value[1]
}/${postData.value[2]}`}
onClick={(event) => { event.stopPropagation(); }}
>
{avatarView}
@ -127,21 +164,21 @@ class Post extends Component {
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span className={this.props.postData
<span className={postData
!== null ? '' : 'grey-text'}
>
<strong>
{this.props.postData !== null
? this.props.postData.value[2]
{postData !== null
? postData.value[2]
: 'Username'
}
</strong>
</span>
<span className="grey-text">
{this.props.postData !== null
{postData !== null
&& (
<TimeAgo date={epochTimeConverter(
this.props.postData.value[3],
postData.value[3],
)}
/>
)
@ -150,11 +187,11 @@ class Post extends Component {
</div>
<div className="stretch-space-between">
<span
className={this.state.postSubject
className={postSubject
=== '' ? '' : 'grey-text'}
>
<strong>
{this.state.postSubject === ''
{postSubject === ''
? (
<ContentLoader
height={5.8}
@ -174,14 +211,14 @@ class Post extends Component {
</ContentLoader>
)
: `Subject: ${
this.state.postSubject}`
postSubject}`
}
</strong>
</span>
</div>
<div className="post-content">
{this.state.postContent !== ''
? <ReactMarkdown source={this.state.postContent} />
{postContent !== ''
? <ReactMarkdown source={postContent} />
: (
<ContentLoader
height={11.2}
@ -231,11 +268,11 @@ class Post extends Component {
<Button
icon
size="mini"
onClick={this.props.postData
onClick={postData
? () => {
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 {
</Transition>
);
}
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);

73
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) => (
<Post
postData={(this.state.dataKeys[postID]
&& this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]])
? this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]]
: null}
avatarUrl=""
postIndex={index}
postID={postID}
getFocus={this.props.focusOnPost === postID}
key={postID}
/>
));
return (
<div>
{this.props.recentToTheTop
? posts.slice(0).reverse()
: posts
}
</div>
);
}
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) => (
<Post
postData={(dataKeys[postID]
&& contracts[contract][getPostMethod][dataKeys[postID]])
? contracts[contract][getPostMethod][dataKeys[postID]]
: null}
avatarUrl=""
postIndex={index}
postID={postID}
getFocus={focusOnPost === postID}
key={postID}
/>
));
return (
<div>
{recentToTheTop
? posts.slice(0).reverse()
: posts
}
</div>
);
}
}
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

84
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 (
<div className="user-info">
{this.props.avatarUrl && (
{avatarUrl && (
<UserAvatar
size="40"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.username}
src={avatarUrl}
name={username}
/>
)}
<table className="highlight centered responsive-table">
<tbody>
<tr>
<td><strong>Username:</strong></td>
<td>{this.props.username}</td>
<td>{username}</td>
</tr>
<tr>
<td><strong>Account address:</strong></td>
<td>{this.props.address}</td>
<td>{address}</td>
</tr>
<tr>
<td><strong>OrbitDB:</strong></td>
<td>{this.state.orbitDBId}</td>
<td>{orbitDBId}</td>
</tr>
<tr>
<td><strong>Number of topics created:</strong></td>
<td>{this.props.numberOfTopics}</td>
<td>{numberOfTopics}</td>
</tr>
<tr>
<td><strong>Number of posts:</strong></td>
<td>{this.props.numberOfPosts}</td>
<td>{numberOfPosts}</td>
</tr>
{this.state.dateOfRegister
{dateOfRegister
&& (
<tr>
<td><strong>Member since:</strong></td>
<td>{epochTimeConverter(this.state.dateOfRegister)}</td>
<td>{epochTimeConverter(dateOfRegister)}</td>
</tr>
)
}
</tbody>
</table>
{this.props.self && <UsernameFormContainer />}
{self && <UsernameFormContainer />}
</div>
);
}
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,

86
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 (
<Card
link
className="card"
onClick={() => {
this.props.history.push(`/topic/${this.props.topicID}`);
history.push(`/topic/${topicID}`);
}}
>
<Card.Content>
<div className={`topic-subject${
this.state.topicSubject ? '' : ' grey-text'}`}
topicSubject ? '' : ' grey-text'}`}
>
<p>
<strong>
{this.state.topicSubject !== null ? this.state.topicSubject
{topicSubject !== null ? topicSubject
: (
<ContentLoader
height={5.8}
@ -82,26 +112,26 @@ class Topic extends Component {
<hr />
<div className="topic-meta">
<p className={`no-margin${
this.props.topicData !== null ? '' : ' grey-text'}`}
topicData !== null ? '' : ' grey-text'}`}
>
{this.props.topicData !== null
? this.props.topicData.value[2]
{topicData !== null
? topicData.value[2]
: 'Username'
}
</p>
<p className={`no-margin${
this.props.topicData !== null ? '' : ' grey-text'}`}
topicData !== null ? '' : ' grey-text'}`}
>
{`Number of replies: ${this.props.topicData !== null
? this.props.topicData.value[4].length
{`Number of replies: ${topicData !== null
? topicData.value[4].length
: ''}`
}
</p>
<p className="topic-date grey-text">
{this.props.topicData !== null
{topicData !== null
&& (
<TimeAgo
date={epochTimeConverter(this.props.topicData.value[3])}
date={epochTimeConverter(topicData.value[3])}
/>
)
}
@ -111,26 +141,16 @@ class Topic extends Component {
</Card>
);
}
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

50
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 => (
<Topic
topicData={(this.state.dataKeys[topicID]
&& this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]])
? this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]]
topicData={(dataKeys[topicID]
&& contracts[contract][getTopicMethod][dataKeys[topicID]])
? contracts[contract][getTopicMethod][dataKeys[topicID]]
: null}
topicID={topicID}
key={topicID}
@ -58,16 +72,14 @@ class TopicList extends Component {
</div>
);
}
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

49
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 = ([
<TopicList topicIDs={this.topicIDs} key="topicList" />,
<div className="bottom-overlay-pad" key="pad" />,
this.props.hasSignedUp
hasSignedUp
&& (
<FloatingButton
onClick={this.handleCreateTopicClick}
@ -69,7 +85,7 @@ class BoardContainer extends Component {
/>
)
]);
} else if (!this.props.hasSignedUp) {
} else if (!hasSignedUp) {
boardContents = (
<div className="vertical-center-in-parent">
<Header color="teal" textAlign="center" as="h2">
@ -105,16 +121,15 @@ class BoardContainer extends Component {
</div>
);
}
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,

51
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 (
<div className="App">
<NavBarContainer />
{/* <div className="progress-bar-container"
style={{display: this.props.isProgressBarVisible ? "block" : "none"}}>
<div className="progress">
<div className="indeterminate"></div>
</div>
</div> */}
<div className="page-container">
<aside className="left-side-panel" />
<div className="main-panel">
<div className="view-container">
{this.props.children}
</div>
</div>
<aside className="right-side-panel">
<RightSideBarContainer />
</aside>
const CoreLayout = ({ children }) => (
<div className="App">
<NavBarContainer />
{/* <div className="progress-bar-container"
style={{display: this.props.isProgressBarVisible ? "block" : "none"}}>
<div className="progress">
<div className="indeterminate"></div>
</div>
</div> */}
<div className="page-container">
<aside className="left-side-panel" />
<div className="main-panel">
<div className="view-container">
{children}
</div>
</div>
);
}
}
<aside className="right-side-panel">
<RightSideBarContainer />
</aside>
</div>
</div>
);
CoreLayout.propTypes = {
children: PropTypes.objectOf(PropTypes.object)
};
export default CoreLayout;

91
app/src/containers/NavBarContainer.js

@ -1,4 +1,5 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { push } from 'connected-react-router';
@ -6,50 +7,52 @@ import { Image, Menu } from 'semantic-ui-react';
import logo from '../assets/images/logo.png';
class NavBarContainer extends Component {
render() {
return (
<Menu fixed="top" inverted>
<Menu.Item header onClick={() => { this.props.navigateTo('/'); }}>
<Image
size="mini"
src={logo}
style={{
marginRight: '1.5em'
}}
/>
Apella
const NavBarContainer = ({ hasSignedUp, navigateTo, navBarTitle }) => (
<Menu fixed="top" inverted>
<Menu.Item header onClick={() => { navigateTo('/'); }}>
<Image
size="mini"
src={logo}
style={{
marginRight: '1.5em'
}}
/>
Apella
</Menu.Item>
<Menu.Item onClick={() => { navigateTo('/home'); }}>
Home
</Menu.Item>
{hasSignedUp
? (
<Menu.Item onClick={() => { navigateTo('/profile'); }}>
Profile
</Menu.Item>
<Menu.Item onClick={() => { this.props.navigateTo('/home'); }}>
Home
</Menu.Item>
{this.props.hasSignedUp
? (
<Menu.Item onClick={() => { this.props.navigateTo('/profile'); }}>
Profile
</Menu.Item>
)
: (
<Menu.Menu
position="right"
style={{
backgroundColor: '#00b5ad'
}}
>
<Menu.Item onClick={() => { this.props.navigateTo('/signup'); }}>
SignUp
</Menu.Item>
</Menu.Menu>
)
}
<div className="navBarText">
{this.props.navBarTitle !== ''
&& <span>{this.props.navBarTitle}</span>}
</div>
</Menu>
);
}
}
)
: (
<Menu.Menu
position="right"
style={{
backgroundColor: '#00b5ad'
}}
>
<Menu.Item onClick={() => { navigateTo('/signup'); }}>
SignUp
</Menu.Item>
</Menu.Menu>
)
}
<div className="navBarText">
{navBarTitle !== ''
&& <span>{navBarTitle}</span>}
</div>
</Menu>
);
NavBarContainer.propTypes = {
hasSignedUp: PropTypes.bool.isRequired,
navigateTo: PropTypes.func.isRequired,
navBarTitle: PropTypes.string.isRequired
};
const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: location => push(location)

106
app/src/containers/ProfileContainer.js

@ -1,11 +1,11 @@
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';
import { Tab } from 'semantic-ui-react';
import { drizzle } from '../index';
import ProfileInformation from '../components/ProfileInformation';
import TopicList from '../components/TopicList';
import PostList from '../components/PostList';
@ -29,12 +29,14 @@ class ProfileContainer extends Component {
constructor(props) {
super(props);
const { match, user } = this.props;
this.getBlockchainData = this.getBlockchainData.bind(this);
this.dataKey = [];
const address = this.props.match.params.address
? this.props.match.params.address
: this.props.user.address;
const address = match.params.address
? match.params.address
: user.address;
this.state = {
pageStatus: 'initialized',
@ -45,12 +47,28 @@ class ProfileContainer extends Component {
};
}
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate() {
this.getBlockchainData();
}
componentWillUnmount() {
const { setNavBarTitle } = this.props;
setNavBarTitle('');
}
getBlockchainData() {
if (this.state.pageStatus === 'initialized'
&& this.props.drizzleStatus.initialized) {
const { userAddress, pageStatus, username, topicIDs, postIDs } = this.state;
const { drizzleStatus, setNavBarTitle, contracts } = this.props;
if (pageStatus === 'initialized'
&& drizzleStatus.initialized) {
callsInfo.forEach((call, index) => {
this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall(
this.state.userAddress,
userAddress,
);
});
this.setState({
@ -58,42 +76,42 @@ class ProfileContainer 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.username === '') {
const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (pageStatus === 'loaded') {
if (username === '') {
const transaction = contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction) {
const username = transaction.value;
this.props.setNavBarTitle(username);
setNavBarTitle(username);
this.setState({
username
});
}
}
if (this.state.topicIDs === null) {
const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (topicIDs === null) {
const transaction = contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction) {
this.setState({
topicIDs: transaction.value
});
}
}
if (this.state.postIDs === null) {
const transaction = this.props.contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]];
if (postIDs === null) {
const transaction = contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]];
if (transaction) {
this.setState({
postIDs: transaction.value
@ -106,33 +124,36 @@ class ProfileContainer extends Component {
}
render() {
if (!this.props.user.hasSignedUp) {
this.props.navigateTo('/signup');
const { userAddress, username, topicIDs, postIDs } = this.state;
const { navigateTo, user } = this.props;
if (!user.hasSignedUp) {
navigateTo('/signup');
return (null);
}
const infoTab = (
<ProfileInformation
address={this.state.userAddress}
username={this.state.username}
numberOfTopics={this.state.topicIDs && this.state.topicIDs.length}
numberOfPosts={this.state.postIDs && this.state.postIDs.length}
self={this.state.userAddress === this.props.user.address}
address={userAddress}
username={username}
numberOfTopics={topicIDs && topicIDs.length}
numberOfPosts={postIDs && postIDs.length}
self={userAddress === user.address}
key="profileInfo"
/>
);
const topicsTab = (
<div className="profile-tab">
{this.state.topicIDs
? <TopicList topicIDs={this.state.topicIDs} />
{topicIDs
? <TopicList topicIDs={topicIDs} />
: <LoadingSpinner />
}
</div>
);
const postsTab = (
<div className="profile-tab">
{this.state.postIDs
? <PostList postIDs={this.state.postIDs} recentToTheTop />
{postIDs
? <PostList postIDs={postIDs} recentToTheTop />
: <LoadingSpinner />
}
</div>
@ -174,20 +195,17 @@ class ProfileContainer extends Component {
</div>
);
}
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)

15
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
? (
<div className="vertical-center-in-parent">
<Header color="teal" textAlign="center" as="h2">
@ -29,7 +33,7 @@ class SignUpContainer extends Component {
<p className="no-margin">
<strong>Account address:</strong>
{' '}
{this.props.user.address}
{user.address}
</p>
<UsernameFormContainer />
</div>
@ -39,6 +43,11 @@ class SignUpContainer extends Component {
}
}
SignUpContainer.propTypes = {
user: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
user: state.user
});

58
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 (
<div>
{this.state.previewEnabled
{previewEnabled
&& (
<NewTopicPreview
date={this.state.previewDate}
subject={this.state.topicSubjectInput}
content={this.state.topicMessageInput}
date={previewDate}
subject={topicSubjectInput}
content={topicMessageInput}
/>
)
}
<Form>
{!this.state.previewEnabled
{!previewEnabled
&& [
<Form.Field key="topicSubjectInput">
<Form.Input
name="topicSubjectInput"
error={this.state.topicSubjectInputEmptySubmit}
error={topicSubjectInputEmptySubmit}
type="text"
value={this.state.topicSubjectInput}
value={topicSubjectInput}
placeholder="Subject"
id="topicSubjectInput"
onChange={this.handleInputChange}
@ -103,10 +113,10 @@ class StartTopicContainer extends Component {
<TextArea
key="topicMessageInput"
name="topicMessageInput"
className={this.state.topicMessageInputEmptySubmit
className={topicMessageInputEmptySubmit
? 'form-textarea-required'
: ''}
value={this.state.topicMessageInput}
value={topicMessageInput}
placeholder="Post"
id="topicMessageInput"
rows={5}
@ -143,6 +153,12 @@ class StartTopicContainer extends Component {
}
}
StartTopicContainer.propTypes = {
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
orbitDB: state.orbitDB,
user: state.user

115
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 = (
(
<div>
<PostList
postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]}
focusOnPost={this.state.postFocus
? this.state.postFocus
postIDs={contracts[contract][getTopicMethod][this.dataKey].value[4]}
focusOnPost={postFocus
? postFocus
: null}
/>
{this.state.posting
{posting
&& (
<NewPost
topicID={this.state.topicID}
subject={this.state.topicSubject}
postIndex={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4].length}
topicID={topicID}
subject={topicSubject}
postIndex={contracts[contract][getTopicMethod][this.dataKey].value[4].length}
onCancelClick={() => { this.togglePostingState(); }}
onPostCreated={() => { this.postCreated(); }}
/>
)
}
<div className="posts-list-spacer" />
{this.props.user.hasSignedUp && !this.state.posting
{user.hasSignedUp && !posting
&& <FloatingButton onClick={this.togglePostingState} />
}
</div>
@ -154,26 +179,24 @@ class TopicContainer extends Component {
return (
<div className="fill">
{topicContents}
{!this.state.posting
{!posting
&& <div className="bottom-overlay-pad" />
}
</div>
);
}
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)

60
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(<br key="confirmed" />);
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(<br key="mined" />);
message.push('- transaction mined');
color = 'green';
message.push(<br key="success" />);
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(<br key="mined" />);
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

68
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 {
<Form.Input
placeholder={placeholderText}
name="usernameInput"
value={this.state.usernameInput}
value={usernameInput}
onChange={this.handleInputChange}
/>
</Form.Field>
<Message
error
header={this.state.errorHeader}
content={this.state.errorMessage}
header={errorHeader}
content={errorMessage}
/>
<Button type="submit">{buttonText}</Button>
</Form>
<Dimmer active={this.state.signingUp} page>
<Dimmer active={signingUp} page>
<Header as="h2" inverted>
<Loader size="large">
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,

3
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),
);

2
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",

Loading…
Cancel
Save