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. 32
      app/src/components/NewTopicPreview.js
  6. 135
      app/src/components/Post.js
  7. 73
      app/src/components/PostList.js
  8. 82
      app/src/components/ProfileInformation.js
  9. 84
      app/src/components/Topic.js
  10. 48
      app/src/components/TopicList.js
  11. 47
      app/src/containers/BoardContainer.js
  12. 15
      app/src/containers/CoreLayoutContainer.js
  13. 29
      app/src/containers/NavBarContainer.js
  14. 104
      app/src/containers/ProfileContainer.js
  15. 15
      app/src/containers/SignUpContainer.js
  16. 58
      app/src/containers/StartTopicContainer.js
  17. 113
      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 React from 'react';
import PropTypes from 'prop-types';
import { Button, Icon } from 'semantic-ui-react'; import { Button, Icon } from 'semantic-ui-react';
const FloatingButton = props => ( const FloatingButton = ({ onClick }) => (
<div className="action-button" onClick={props.onClick}> <div className="action-button" role="button" onClick={onClick} onKeyUp={onClick} tabIndex={0}>
<Button icon color="teal" size="large"> <Button icon color="teal" size="large">
<Icon name="add" /> <Icon name="add" />
</Button> </Button>
</div> </div>
); );
FloatingButton.propTypes = {
onClick: PropTypes.func.isRequired
};
export default FloatingButton; export default FloatingButton;

12
app/src/components/LoadingSpinner.js

@ -1,11 +1,12 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
const LoadingSpinner = props => ( const LoadingSpinner = ({ className, style }) => (
<div className="vertical-center-children"> <div className="vertical-center-children">
<div <div
className={`center-in-parent ${ className={`center-in-parent ${
props.className ? props.className : ''}`} className ? className : ''}`}
style={props.style ? props.style : []} style={style ? style : []}
> >
<p> <p>
<i className="fas fa-spinner fa-3x fa-spin" /> <i className="fas fa-spinner fa-3x fa-spin" />
@ -14,4 +15,9 @@ const LoadingSpinner = props => (
</div> </div>
); );
LoadingSpinner.propTypes = {
className: PropTypes.string,
style: PropTypes.string
};
export default LoadingSpinner; export default LoadingSpinner;

108
app/src/components/NewPost.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Button, Divider, Form, Grid, Icon, TextArea } from 'semantic-ui-react'; 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'; import { createPost } from '../redux/actions/transactionsActions';
class NewPost extends Component { class NewPost extends Component {
constructor(props, context) { constructor(props) {
super(props); super(props);
const { subject } = props;
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
this.handlePreviewToggle = this.handlePreviewToggle.bind(this); this.handlePreviewToggle = this.handlePreviewToggle.bind(this);
@ -20,7 +22,7 @@ class NewPost extends Component {
this.newPostOuterRef = React.createRef(); this.newPostOuterRef = React.createRef();
this.state = { this.state = {
postSubjectInput: this.props.subject ? this.props.subject : '', postSubjectInput: subject ? subject : '',
postContentInput: '', postContentInput: '',
postSubjectInputEmptySubmit: false, postSubjectInputEmptySubmit: false,
postContentInputEmptySubmit: 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() { 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({ this.setState({
postSubjectInputEmptySubmit: this.state.postSubjectInput === '', postSubjectInputEmptySubmit: postSubjectInput === '',
postContentInputEmptySubmit: this.state.postContentInput === '' postContentInputEmptySubmit: postContentInput === ''
}); });
return; return;
} }
this.props.dispatch( dispatch(
createPost(this.props.topicID, createPost(topicID,
{ {
postSubject: this.state.postSubjectInput, postSubject: postSubjectInput,
postMessage: this.state.postContentInput postMessage: postContentInput
}), }),
); );
this.props.onPostCreated(); onPostCreated();
} }
handleInputChange(event) { handleInputChange(event) {
@ -56,29 +75,25 @@ class NewPost extends Component {
} }
handlePreviewToggle() { handlePreviewToggle() {
this.setState((prevState, props) => ({ this.setState(prevState => ({
previewEnabled: !prevState.previewEnabled, previewEnabled: !prevState.previewEnabled,
previewDate: this.getDate() previewDate: this.getDate()
})); }));
} }
getDate() {
const currentdate = new Date();
return (`${currentdate.getMonth() + 1} ${
currentdate.getDate()}, ${
currentdate.getFullYear()}, ${
currentdate.getHours()}:${
currentdate.getMinutes()}:${
currentdate.getSeconds()}`);
}
render() { render() {
const {
previewDate, postSubjectInputEmptySubmit, postSubjectInput, postContentInputEmptySubmit,
postContentInput, previewEnabled
} = this.state;
const { postIndex, avatarUrl, user, onCancelClick } = this.props;
return ( return (
<div className="post" ref={this.newPostOuterRef}> <div className="post" ref={this.newPostOuterRef}>
<Divider horizontal> <Divider horizontal>
<span className="grey-text"> <span className="grey-text">
# #
{this.props.postIndex} {postIndex}
</span> </span>
</Divider> </Divider>
<Grid> <Grid>
@ -87,39 +102,39 @@ class NewPost extends Component {
<UserAvatar <UserAvatar
size="52" size="52"
className="inline user-avatar" className="inline user-avatar"
src={this.props.avatarUrl} src={avatarUrl}
name={this.props.user.username} name={user.username}
/> />
</Grid.Column> </Grid.Column>
<Grid.Column width={15}> <Grid.Column width={15}>
<div className=""> <div className="">
<div className="stretch-space-between"> <div className="stretch-space-between">
<span><strong>{this.props.user.username}</strong></span> <span><strong>{user.username}</strong></span>
<span className="grey-text"> <span className="grey-text">
{this.state.previewEnabled {previewEnabled
&& <TimeAgo date={this.state.previewDate} /> && <TimeAgo date={previewDate} />
} }
</span> </span>
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
<span> <span>
<strong> <strong>
{this.state.previewEnabled {previewEnabled
&& (`Subject: ${ && (`Subject: ${
this.state.postSubjectInput}`) postSubjectInput}`)
} }
</strong> </strong>
</span> </span>
</div> </div>
<div className="post-content"> <div className="post-content">
<div style={{ <div style={{
display: this.state.previewEnabled display: previewEnabled
? 'block' ? 'block'
: 'none' : 'none'
}} }}
> >
<ReactMarkdown <ReactMarkdown
source={this.state.postContentInput} source={postContentInput}
className="markdown-preview" className="markdown-preview"
/> />
</div> </div>
@ -127,14 +142,14 @@ class NewPost extends Component {
<Form.Input <Form.Input
key="postSubjectInput" key="postSubjectInput"
style={{ style={{
display: this.state.previewEnabled display: previewEnabled
? 'none' ? 'none'
: '' : ''
}} }}
name="postSubjectInput" name="postSubjectInput"
error={this.state.postSubjectInputEmptySubmit} error={postSubjectInputEmptySubmit}
type="text" type="text"
value={this.state.postSubjectInput} value={postSubjectInput}
placeholder="Subject" placeholder="Subject"
id="postSubjectInput" id="postSubjectInput"
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -142,15 +157,15 @@ class NewPost extends Component {
<TextArea <TextArea
key="postContentInput" key="postContentInput"
style={{ style={{
display: this.state.previewEnabled display: previewEnabled
? 'none' ? 'none'
: '' : ''
}} }}
name="postContentInput" name="postContentInput"
className={this.state.postContentInputEmptySubmit className={postContentInputEmptySubmit
? 'form-textarea-required' ? 'form-textarea-required'
: ''} : ''}
value={this.state.postContentInput} value={postContentInput}
placeholder="Post" placeholder="Post"
id="postContentInput" id="postContentInput"
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -177,11 +192,11 @@ class NewPost extends Component {
onClick={this.handlePreviewToggle} onClick={this.handlePreviewToggle}
color="yellow" color="yellow"
> >
{this.state.previewEnabled ? 'Edit' : 'Preview'} {previewEnabled ? 'Edit' : 'Preview'}
</Button> </Button>
<Button <Button
type="button" type="button"
onClick={this.props.onCancelClick} onClick={onCancelClick}
color="red" color="red"
> >
Cancel Cancel
@ -196,12 +211,19 @@ class NewPost extends Component {
</div> </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 => ({ const mapStateToProps = state => ({
orbitDB: state.orbitDB, orbitDB: state.orbitDB,
user: state.user user: state.user

32
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 { connect } from 'react-redux';
import { Divider, Grid } from 'semantic-ui-react'; import { Divider, Grid } from 'semantic-ui-react';
@ -7,13 +8,7 @@ import TimeAgo from 'react-timeago';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
class Post extends Component { const Post = ({ user, date, subject, content }) => (
constructor(props, context) {
super(props);
}
render() {
return (
<div className="post"> <div className="post">
<Divider horizontal> <Divider horizontal>
<span className="grey-text">#0</span> <span className="grey-text">#0</span>
@ -24,8 +19,8 @@ class Post extends Component {
<UserAvatar <UserAvatar
size="52" size="52"
className="inline" className="inline"
src={this.props.user.avatarUrl} src={user.avatarUrl}
name={this.props.user.username} name={user.username}
/> />
</Grid.Column> </Grid.Column>
<Grid.Column width={15}> <Grid.Column width={15}>
@ -33,11 +28,11 @@ class Post extends Component {
<div className="stretch-space-between"> <div className="stretch-space-between">
<span> <span>
<strong> <strong>
{this.props.user.username} {user.username}
</strong> </strong>
</span> </span>
<span className="grey-text"> <span className="grey-text">
<TimeAgo date={this.props.date} /> <TimeAgo date={date} />
</span> </span>
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
@ -45,12 +40,12 @@ class Post extends Component {
<strong> <strong>
Subject: Subject:
{' '} {' '}
{this.props.subject} {subject}
</strong> </strong>
</span> </span>
</div> </div>
<div className="post-content"> <div className="post-content">
<ReactMarkdown source={this.props.content} /> <ReactMarkdown source={content} />
</div> </div>
</div> </div>
</Grid.Column> </Grid.Column>
@ -58,8 +53,13 @@ class Post extends Component {
</Grid> </Grid>
</div> </div>
); );
}
} Post.propTypes = {
subject: PropTypes.string,
date: PropTypes.string,
content: PropTypes.string,
user: PropTypes.object.isRequired
};
const mapStateToProps = state => ({ const mapStateToProps = state => ({
user: state.user user: state.user

135
app/src/components/Post.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import { Link, withRouter } from 'react-router-dom'; import { Link, withRouter } from 'react-router-dom';
@ -16,9 +17,11 @@ class Post extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { getFocus } = props;
this.getBlockchainData = this.getBlockchainData.bind(this); this.getBlockchainData = this.getBlockchainData.bind(this);
this.fetchPost = this.fetchPost.bind(this); this.fetchPost = this.fetchPost.bind(this);
if (props.getFocus) { if (getFocus) {
this.postRef = React.createRef(); 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() { getBlockchainData() {
if (this.props.postData const { fetchPostDataStatus } = this.state;
&& this.props.orbitDB.orbitdb const { postData, orbitDB, postID } = this.props;
&& this.state.fetchPostDataStatus === 'pending') {
if (postData && orbitDB.orbitdb && fetchPostDataStatus === 'pending') {
this.setState({ this.setState({
fetchPostDataStatus: 'fetching' fetchPostDataStatus: 'fetching'
}); });
this.fetchPost(this.props.postID); this.fetchPost(postID);
} }
} }
async fetchPost(postID) { async fetchPost(postID) {
const { user, postData, orbitDB } = this.props;
let orbitPostData; 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 { } else {
const fullAddress = `/orbitdb/${this.props.postData.value[0]}/posts`; const fullAddress = `/orbitdb/${postData.value[0]}/posts`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); const store = await orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
const localOrbitData = store.get(postID); const localOrbitData = store.get(postID);
@ -71,13 +105,16 @@ class Post extends Component {
} }
render() { render() {
const avatarView = (this.props.postData const { animateOnToggle, postSubject, postContent } = this.state;
const { avatarUrl, postIndex, navigateTo, postData, postID } = this.props;
const avatarView = (postData
? ( ? (
<UserAvatar <UserAvatar
size="52" size="52"
className="inline" className="inline"
src={this.props.avatarUrl} src={avatarUrl}
name={this.props.postData.value[2]} name={postData.value[2]}
/> />
) )
: ( : (
@ -99,23 +136,23 @@ class Post extends Component {
<Transition <Transition
animation="tada" animation="tada"
duration={500} duration={500}
visible={this.state.animateOnToggle} visible={animateOnToggle}
> >
<div className="post" ref={this.postRef ? this.postRef : null}> <div className="post" ref={this.postRef ? this.postRef : null}>
<Divider horizontal> <Divider horizontal>
<span className="grey-text"> <span className="grey-text">
# #
{this.props.postIndex} {postIndex}
</span> </span>
</Divider> </Divider>
<Grid> <Grid>
<Grid.Row columns={16} stretched> <Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar"> <Grid.Column width={1} className="user-avatar">
{this.props.postData !== null {postData !== null
? ( ? (
<Link <Link
to={`/profile/${this.props.postData.value[1] to={`/profile/${postData.value[1]
}/${this.props.postData.value[2]}`} }/${postData.value[2]}`}
onClick={(event) => { event.stopPropagation(); }} onClick={(event) => { event.stopPropagation(); }}
> >
{avatarView} {avatarView}
@ -127,21 +164,21 @@ class Post extends Component {
<Grid.Column width={15}> <Grid.Column width={15}>
<div className=""> <div className="">
<div className="stretch-space-between"> <div className="stretch-space-between">
<span className={this.props.postData <span className={postData
!== null ? '' : 'grey-text'} !== null ? '' : 'grey-text'}
> >
<strong> <strong>
{this.props.postData !== null {postData !== null
? this.props.postData.value[2] ? postData.value[2]
: 'Username' : 'Username'
} }
</strong> </strong>
</span> </span>
<span className="grey-text"> <span className="grey-text">
{this.props.postData !== null {postData !== null
&& ( && (
<TimeAgo date={epochTimeConverter( <TimeAgo date={epochTimeConverter(
this.props.postData.value[3], postData.value[3],
)} )}
/> />
) )
@ -150,11 +187,11 @@ class Post extends Component {
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
<span <span
className={this.state.postSubject className={postSubject
=== '' ? '' : 'grey-text'} === '' ? '' : 'grey-text'}
> >
<strong> <strong>
{this.state.postSubject === '' {postSubject === ''
? ( ? (
<ContentLoader <ContentLoader
height={5.8} height={5.8}
@ -174,14 +211,14 @@ class Post extends Component {
</ContentLoader> </ContentLoader>
) )
: `Subject: ${ : `Subject: ${
this.state.postSubject}` postSubject}`
} }
</strong> </strong>
</span> </span>
</div> </div>
<div className="post-content"> <div className="post-content">
{this.state.postContent !== '' {postContent !== ''
? <ReactMarkdown source={this.state.postContent} /> ? <ReactMarkdown source={postContent} />
: ( : (
<ContentLoader <ContentLoader
height={11.2} height={11.2}
@ -231,11 +268,11 @@ class Post extends Component {
<Button <Button
icon icon
size="mini" size="mini"
onClick={this.props.postData onClick={postData
? () => { ? () => {
this.props.navigateTo(`/topic/${ navigateTo(`/topic/${
this.props.postData.value[4]}/${ postData.value[4]}/${
this.props.postID}`); postID}`);
} }
: () => {}} : () => {}}
> >
@ -248,34 +285,18 @@ class Post extends Component {
</Transition> </Transition>
); );
} }
componentDidMount() {
this.getBlockchainData();
} }
componentDidUpdate() { Post.propTypes = {
this.getBlockchainData(); getFocus: PropTypes.bool.isRequired,
if (this.state.readyForAnimation) { user: PropTypes.object.isRequired,
if (this.postRef) { orbitDB: PropTypes.object.isRequired,
setTimeout(() => { avatarUrl: PropTypes.string,
this.postRef.current.scrollIntoView( postIndex: PropTypes.number.isRequired,
{ navigateTo: PropTypes.func.isRequired,
block: 'start', behavior: 'smooth' postData: PropTypes.object,
}, postID: PropTypes.string.isRequired
); };
setTimeout(() => {
this.setState({
animateOnToggle: false
});
}, 300);
}, 100);
this.setState({
readyForAnimation: false
});
}
}
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: location => push(location) navigateTo: location => push(location)

73
app/src/components/PostList.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { drizzle } from '../index'; 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() { componentDidMount() {
this.getBlockchainData(); this.getBlockchainData();
} }
@ -52,12 +28,15 @@ class PostList extends Component {
} }
getBlockchainData() { getBlockchainData() {
if (this.props.drizzleStatus.initialized) { const { dataKeys } = this.state;
const dataKeysShallowCopy = this.state.dataKeys.slice(); const { drizzleStatus, postIDs } = this.props;
if (drizzleStatus.initialized) {
const dataKeysShallowCopy = dataKeys.slice();
let fetchingNewData = false; let fetchingNewData = false;
this.props.postIDs.forEach((postID) => { postIDs.forEach((postID) => {
if (!this.state.dataKeys[postID]) { if (!dataKeys[postID]) {
dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall( dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(
postID, 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 => ({ const mapStateToProps = state => ({
contracts: state.contracts, contracts: state.contracts,
drizzleStatus: state.drizzleStatus drizzleStatus: state.drizzleStatus

82
app/src/components/ProfileInformation.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
import { drizzle } from '../index'; import { drizzle } from '../index';
@ -30,12 +31,23 @@ class ProfileInformation extends Component {
}; };
} }
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate() {
this.getBlockchainData();
}
getBlockchainData() { getBlockchainData() {
if (this.state.pageStatus === 'initialized' const { pageStatus, dateOfRegister, orbitDBId } = this.state;
&& this.props.drizzleStatus.initialized) { const { drizzleStatus, address, contracts } = this.props;
if (pageStatus === 'initialized'
&& drizzleStatus.initialized) {
callsInfo.forEach((call, index) => { callsInfo.forEach((call, index) => {
this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall( this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall(
this.props.address, address,
); );
}); });
this.setState({ this.setState({
@ -43,32 +55,32 @@ class ProfileInformation extends Component {
}); });
} }
if (this.state.pageStatus === 'loading') { if (pageStatus === 'loading') {
let pageStatus = 'loaded'; let pageStatusUpdate = 'loaded';
callsInfo.forEach((call, index) => { callsInfo.forEach((call, index) => {
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) { if (!contracts[call.contract][call.method][this.dataKey[index]]) {
pageStatus = 'loading'; pageStatusUpdate = 'loading';
} }
}); });
if (pageStatus === 'loaded') { if (pageStatusUpdate === 'loaded') {
this.setState({ this.setState({
pageStatus pageStatus: pageStatusUpdate
}); });
} }
} }
if (this.state.pageStatus === 'loaded') { if (pageStatus === 'loaded') {
if (this.state.dateOfRegister === '') { if (dateOfRegister === '') {
const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; const transaction = contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction) { if (transaction) {
this.setState({ this.setState({
dateOfRegister: transaction.value dateOfRegister: transaction.value
}); });
} }
} }
if (this.state.orbitDBId === '') { if (orbitDBId === '') {
const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; const transaction = contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction) { if (transaction) {
this.setState({ this.setState({
orbitDBId: transaction.value orbitDBId: transaction.value
@ -79,61 +91,67 @@ class ProfileInformation extends Component {
} }
render() { render() {
const { orbitDBId, dateOfRegister } = this.state;
const { avatarUrl, username, address, numberOfTopics, numberOfPosts, self } = this.props;
return ( return (
<div className="user-info"> <div className="user-info">
{this.props.avatarUrl && ( {avatarUrl && (
<UserAvatar <UserAvatar
size="40" size="40"
className="inline user-avatar" className="inline user-avatar"
src={this.props.avatarUrl} src={avatarUrl}
name={this.props.username} name={username}
/> />
)} )}
<table className="highlight centered responsive-table"> <table className="highlight centered responsive-table">
<tbody> <tbody>
<tr> <tr>
<td><strong>Username:</strong></td> <td><strong>Username:</strong></td>
<td>{this.props.username}</td> <td>{username}</td>
</tr> </tr>
<tr> <tr>
<td><strong>Account address:</strong></td> <td><strong>Account address:</strong></td>
<td>{this.props.address}</td> <td>{address}</td>
</tr> </tr>
<tr> <tr>
<td><strong>OrbitDB:</strong></td> <td><strong>OrbitDB:</strong></td>
<td>{this.state.orbitDBId}</td> <td>{orbitDBId}</td>
</tr> </tr>
<tr> <tr>
<td><strong>Number of topics created:</strong></td> <td><strong>Number of topics created:</strong></td>
<td>{this.props.numberOfTopics}</td> <td>{numberOfTopics}</td>
</tr> </tr>
<tr> <tr>
<td><strong>Number of posts:</strong></td> <td><strong>Number of posts:</strong></td>
<td>{this.props.numberOfPosts}</td> <td>{numberOfPosts}</td>
</tr> </tr>
{this.state.dateOfRegister {dateOfRegister
&& ( && (
<tr> <tr>
<td><strong>Member since:</strong></td> <td><strong>Member since:</strong></td>
<td>{epochTimeConverter(this.state.dateOfRegister)}</td> <td>{epochTimeConverter(dateOfRegister)}</td>
</tr> </tr>
) )
} }
</tbody> </tbody>
</table> </table>
{this.props.self && <UsernameFormContainer />} {self && <UsernameFormContainer />}
</div> </div>
); );
} }
componentDidMount() {
this.getBlockchainData();
} }
componentDidUpdate() { ProfileInformation.propTypes = {
this.getBlockchainData(); 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 => ({ const mapStateToProps = state => ({
drizzleStatus: state.drizzleStatus, drizzleStatus: state.drizzleStatus,

84
app/src/components/Topic.js

@ -1,4 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { GetTopicResult } from '../CustomPropTypes'
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; 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) { async fetchSubject(topicID) {
const { topicData, user, orbitDB } = this.props;
let topicSubject; let topicSubject;
if (this.props.topicData.value[1] === this.props.user.address) { if (topicData.value[1] === user.address) {
const orbitData = this.props.orbitDB.topicsDB.get(topicID); const orbitData = orbitDB.topicsDB.get(topicID);
topicSubject = orbitData.subject; topicSubject = orbitData.subject;
} else { } else {
const fullAddress = `/orbitdb/${this.props.topicData.value[0] const fullAddress = `/orbitdb/${topicData.value[0]
}/topics`; }/topics`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); const store = await orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
const localOrbitData = store.get(topicID); const localOrbitData = store.get(topicID);
@ -50,21 +77,24 @@ class Topic extends Component {
} }
render() { render() {
const { topicSubject } = this.state;
const { history, topicID, topicData } = this.props;
return ( return (
<Card <Card
link link
className="card" className="card"
onClick={() => { onClick={() => {
this.props.history.push(`/topic/${this.props.topicID}`); history.push(`/topic/${topicID}`);
}} }}
> >
<Card.Content> <Card.Content>
<div className={`topic-subject${ <div className={`topic-subject${
this.state.topicSubject ? '' : ' grey-text'}`} topicSubject ? '' : ' grey-text'}`}
> >
<p> <p>
<strong> <strong>
{this.state.topicSubject !== null ? this.state.topicSubject {topicSubject !== null ? topicSubject
: ( : (
<ContentLoader <ContentLoader
height={5.8} height={5.8}
@ -82,26 +112,26 @@ class Topic extends Component {
<hr /> <hr />
<div className="topic-meta"> <div className="topic-meta">
<p className={`no-margin${ <p className={`no-margin${
this.props.topicData !== null ? '' : ' grey-text'}`} topicData !== null ? '' : ' grey-text'}`}
> >
{this.props.topicData !== null {topicData !== null
? this.props.topicData.value[2] ? topicData.value[2]
: 'Username' : 'Username'
} }
</p> </p>
<p className={`no-margin${ <p className={`no-margin${
this.props.topicData !== null ? '' : ' grey-text'}`} topicData !== null ? '' : ' grey-text'}`}
> >
{`Number of replies: ${this.props.topicData !== null {`Number of replies: ${topicData !== null
? this.props.topicData.value[4].length ? topicData.value[4].length
: ''}` : ''}`
} }
</p> </p>
<p className="topic-date grey-text"> <p className="topic-date grey-text">
{this.props.topicData !== null {topicData !== null
&& ( && (
<TimeAgo <TimeAgo
date={epochTimeConverter(this.props.topicData.value[3])} date={epochTimeConverter(topicData.value[3])}
/> />
) )
} }
@ -111,25 +141,15 @@ class Topic extends Component {
</Card> </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() { Topic.propTypes = {
if (this.props.topicData !== null user: PropTypes.object.isRequired,
&& this.state.topicSubjectFetchStatus === 'pending' history: PropTypes.object.isRequired,
&& this.props.orbitDB.ipfsInitialized topicData: GetTopicResult.isRequired,
&& this.props.orbitDB.orbitdb) { orbitDB: PropTypes.object.isRequired,
this.fetchSubject(this.props.topicID); topicID: PropTypes.string.isRequired
} };
}
}
const mapStateToProps = state => ({ const mapStateToProps = state => ({
user: state.user, user: state.user,

48
app/src/components/TopicList.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { drizzle } from '../index'; import { drizzle } from '../index';
@ -18,16 +19,26 @@ class TopicList extends Component {
}; };
} }
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate() {
this.getBlockchainData();
}
getBlockchainData() { getBlockchainData() {
if (this.props.drizzleStatus.initialized) { const { dataKeys } = this.state;
const dataKeysShallowCopy = this.state.dataKeys.slice(); const { drizzleStatus, topicIDs } = this.props;
if (drizzleStatus.initialized) {
const dataKeysShallowCopy = dataKeys.slice();
let fetchingNewData = false; let fetchingNewData = false;
this.props.topicIDs.forEach((topicID) => { topicIDs.forEach((topicID) => {
if (!this.state.dataKeys[topicID]) { if (!dataKeys[topicID]) {
dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall( dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod]
topicID, .cacheCall(topicID);
);
fetchingNewData = true; fetchingNewData = true;
} }
}); });
@ -41,11 +52,14 @@ class TopicList extends Component {
} }
render() { render() {
const topics = this.props.topicIDs.map(topicID => ( const { dataKeys } = this.state;
const { topicIDs, contracts } = this.props;
const topics = topicIDs.map(topicID => (
<Topic <Topic
topicData={(this.state.dataKeys[topicID] topicData={(dataKeys[topicID]
&& this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]]) && contracts[contract][getTopicMethod][dataKeys[topicID]])
? this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]] ? contracts[contract][getTopicMethod][dataKeys[topicID]]
: null} : null}
topicID={topicID} topicID={topicID}
key={topicID} key={topicID}
@ -58,15 +72,13 @@ class TopicList extends Component {
</div> </div>
); );
} }
componentDidMount() {
this.getBlockchainData();
} }
componentDidUpdate() { TopicList.propTypes = {
this.getBlockchainData(); topicIDs: PropTypes.arrayOf(PropTypes.string).isRequired,
} contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
} drizzleStatus: PropTypes.object.isRequired
};
const mapStateToProps = state => ({ const mapStateToProps = state => ({
contracts: state.contracts, contracts: state.contracts,

47
app/src/containers/BoardContainer.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
@ -27,16 +28,27 @@ class BoardContainer extends Component {
}; };
} }
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate() {
this.getBlockchainData();
}
getBlockchainData() { getBlockchainData() {
if (this.state.pageStatus === 'initialized' const { pageStatus } = this.state;
&& this.props.drizzleStatus.initialized) { const { drizzleStatus, contracts } = this.props;
if (pageStatus === 'initialized'
&& drizzleStatus.initialized) {
this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall(); this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall();
this.setState({ this.setState({
pageStatus: 'loading' pageStatus: 'loading'
}); });
} }
if (this.state.pageStatus === 'loading' if (pageStatus === 'loading'
&& this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]) { && contracts[contract][getNumberOfTopicsMethod][this.dataKey]) {
this.setState({ this.setState({
pageStatus: 'loaded' pageStatus: 'loaded'
}); });
@ -45,13 +57,17 @@ class BoardContainer extends Component {
} }
handleCreateTopicClick() { handleCreateTopicClick() {
this.props.history.push('/startTopic'); const { history } = this.props;
history.push('/startTopic');
} }
render() { render() {
const { pageStatus } = this.state;
const { contracts, hasSignedUp } = this.props;
let boardContents; let boardContents;
if (this.state.pageStatus === 'loaded') { if (pageStatus === 'loaded') {
const numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; const numberOfTopics = contracts[contract][getNumberOfTopicsMethod][this.dataKey].value;
if (numberOfTopics !== '0') { if (numberOfTopics !== '0') {
this.topicIDs = []; this.topicIDs = [];
@ -61,7 +77,7 @@ class BoardContainer extends Component {
boardContents = ([ boardContents = ([
<TopicList topicIDs={this.topicIDs} key="topicList" />, <TopicList topicIDs={this.topicIDs} key="topicList" />,
<div className="bottom-overlay-pad" key="pad" />, <div className="bottom-overlay-pad" key="pad" />,
this.props.hasSignedUp hasSignedUp
&& ( && (
<FloatingButton <FloatingButton
onClick={this.handleCreateTopicClick} onClick={this.handleCreateTopicClick}
@ -69,7 +85,7 @@ class BoardContainer extends Component {
/> />
) )
]); ]);
} else if (!this.props.hasSignedUp) { } else if (!hasSignedUp) {
boardContents = ( boardContents = (
<div className="vertical-center-in-parent"> <div className="vertical-center-in-parent">
<Header color="teal" textAlign="center" as="h2"> <Header color="teal" textAlign="center" as="h2">
@ -105,15 +121,14 @@ class BoardContainer extends Component {
</div> </div>
); );
} }
componentDidMount() {
this.getBlockchainData();
} }
componentDidUpdate() { BoardContainer.propTypes = {
this.getBlockchainData(); drizzleStatus: PropTypes.object.isRequired,
} history: PropTypes.object.isRequired,
} contracts: PropTypes.objectOf(PropTypes.object).isRequired,
hasSignedUp: PropTypes.bool.isRequired
};
const mapStateToProps = state => ({ const mapStateToProps = state => ({
contracts: state.contracts, contracts: state.contracts,

15
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 NavBarContainer from './NavBarContainer';
import RightSideBarContainer from './TransactionsMonitorContainer'; import RightSideBarContainer from './TransactionsMonitorContainer';
@ -14,9 +15,7 @@ import '../assets/css/profile-container.css';
/* import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer'; */ /* import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer'; */
class CoreLayout extends Component { const CoreLayout = ({ children }) => (
render() {
return (
<div className="App"> <div className="App">
<NavBarContainer /> <NavBarContainer />
{/* <div className="progress-bar-container" {/* <div className="progress-bar-container"
@ -29,7 +28,7 @@ class CoreLayout extends Component {
<aside className="left-side-panel" /> <aside className="left-side-panel" />
<div className="main-panel"> <div className="main-panel">
<div className="view-container"> <div className="view-container">
{this.props.children} {children}
</div> </div>
</div> </div>
<aside className="right-side-panel"> <aside className="right-side-panel">
@ -38,7 +37,9 @@ class CoreLayout extends Component {
</div> </div>
</div> </div>
); );
}
} CoreLayout.propTypes = {
children: PropTypes.objectOf(PropTypes.object)
};
export default CoreLayout; export default CoreLayout;

29
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 { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
@ -6,11 +7,9 @@ import { Image, Menu } from 'semantic-ui-react';
import logo from '../assets/images/logo.png'; import logo from '../assets/images/logo.png';
class NavBarContainer extends Component { const NavBarContainer = ({ hasSignedUp, navigateTo, navBarTitle }) => (
render() {
return (
<Menu fixed="top" inverted> <Menu fixed="top" inverted>
<Menu.Item header onClick={() => { this.props.navigateTo('/'); }}> <Menu.Item header onClick={() => { navigateTo('/'); }}>
<Image <Image
size="mini" size="mini"
src={logo} src={logo}
@ -20,12 +19,12 @@ class NavBarContainer extends Component {
/> />
Apella Apella
</Menu.Item> </Menu.Item>
<Menu.Item onClick={() => { this.props.navigateTo('/home'); }}> <Menu.Item onClick={() => { navigateTo('/home'); }}>
Home Home
</Menu.Item> </Menu.Item>
{this.props.hasSignedUp {hasSignedUp
? ( ? (
<Menu.Item onClick={() => { this.props.navigateTo('/profile'); }}> <Menu.Item onClick={() => { navigateTo('/profile'); }}>
Profile Profile
</Menu.Item> </Menu.Item>
) )
@ -36,20 +35,24 @@ class NavBarContainer extends Component {
backgroundColor: '#00b5ad' backgroundColor: '#00b5ad'
}} }}
> >
<Menu.Item onClick={() => { this.props.navigateTo('/signup'); }}> <Menu.Item onClick={() => { navigateTo('/signup'); }}>
SignUp SignUp
</Menu.Item> </Menu.Item>
</Menu.Menu> </Menu.Menu>
) )
} }
<div className="navBarText"> <div className="navBarText">
{this.props.navBarTitle !== '' {navBarTitle !== ''
&& <span>{this.props.navBarTitle}</span>} && <span>{navBarTitle}</span>}
</div> </div>
</Menu> </Menu>
); );
}
} NavBarContainer.propTypes = {
hasSignedUp: PropTypes.bool.isRequired,
navigateTo: PropTypes.func.isRequired,
navBarTitle: PropTypes.string.isRequired
};
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: location => push(location) navigateTo: location => push(location)

104
app/src/containers/ProfileContainer.js

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

15
app/src/containers/SignUpContainer.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Header } from 'semantic-ui-react'; import { Header } from 'semantic-ui-react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -6,12 +7,15 @@ import UsernameFormContainer from './UsernameFormContainer';
class SignUpContainer extends Component { class SignUpContainer extends Component {
componentDidUpdate(prevProps) { 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() { render() {
const { user } = this.props;
return ( return (
this.props.user.hasSignedUp user.hasSignedUp
? ( ? (
<div className="vertical-center-in-parent"> <div className="vertical-center-in-parent">
<Header color="teal" textAlign="center" as="h2"> <Header color="teal" textAlign="center" as="h2">
@ -29,7 +33,7 @@ class SignUpContainer extends Component {
<p className="no-margin"> <p className="no-margin">
<strong>Account address:</strong> <strong>Account address:</strong>
{' '} {' '}
{this.props.user.address} {user.address}
</p> </p>
<UsernameFormContainer /> <UsernameFormContainer />
</div> </div>
@ -39,6 +43,11 @@ class SignUpContainer extends Component {
} }
} }
SignUpContainer.propTypes = {
user: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
const mapStateToProps = state => ({ const mapStateToProps = state => ({
user: state.user user: state.user
}); });

58
app/src/containers/StartTopicContainer.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Button, Form, Icon, TextArea } from 'semantic-ui-react'; import { Button, Form, Icon, TextArea } from 'semantic-ui-react';
@ -7,7 +8,7 @@ import NewTopicPreview from '../components/NewTopicPreview';
import { createTopic } from '../redux/actions/transactionsActions'; import { createTopic } from '../redux/actions/transactionsActions';
class StartTopicContainer extends Component { class StartTopicContainer extends Component {
constructor(props, context) { constructor(props) {
super(props); super(props);
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
@ -25,24 +26,27 @@ class StartTopicContainer extends Component {
} }
async validateAndPost() { async validateAndPost() {
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput const { topicSubjectInput, topicMessageInput } = this.state;
const { dispatch, history } = this.props;
if (topicSubjectInput === '' || topicMessageInput
=== '') { === '') {
this.setState({ this.setState({
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', topicSubjectInputEmptySubmit: topicSubjectInput === '',
topicMessageInputEmptySubmit: this.state.topicMessageInput === '' topicMessageInputEmptySubmit: topicMessageInput === ''
}); });
return; return;
} }
this.props.dispatch( dispatch(
createTopic( createTopic(
{ {
topicSubject: this.state.topicSubjectInput, topicSubject: topicSubjectInput,
topicMessage: this.state.topicMessageInput topicMessage: topicMessageInput
}, },
), ),
); );
this.props.history.push('/home'); history.push('/home');
} }
handleInputChange(event) { handleInputChange(event) {
@ -52,7 +56,7 @@ class StartTopicContainer extends Component {
} }
handlePreviewToggle() { handlePreviewToggle() {
this.setState((prevState, props) => ({ this.setState((prevState) => ({
previewEnabled: !prevState.previewEnabled, previewEnabled: !prevState.previewEnabled,
previewDate: this.getDate() previewDate: this.getDate()
})); }));
@ -69,32 +73,38 @@ class StartTopicContainer extends Component {
} }
render() { render() {
if (!this.props.user.hasSignedUp) { const {
this.props.history.push('/signup'); previewDate, previewEnabled, topicSubjectInputEmptySubmit, topicSubjectInput,
topicMessageInputEmptySubmit, topicMessageInput
} = this.state;
const { user, history } = this.props;
if (!user.hasSignedUp) {
history.push('/signup');
return (null); return (null);
} }
const previewEditText = this.state.previewEnabled ? 'Edit' : 'Preview'; const previewEditText = previewEnabled ? 'Edit' : 'Preview';
return ( return (
<div> <div>
{this.state.previewEnabled {previewEnabled
&& ( && (
<NewTopicPreview <NewTopicPreview
date={this.state.previewDate} date={previewDate}
subject={this.state.topicSubjectInput} subject={topicSubjectInput}
content={this.state.topicMessageInput} content={topicMessageInput}
/> />
) )
} }
<Form> <Form>
{!this.state.previewEnabled {!previewEnabled
&& [ && [
<Form.Field key="topicSubjectInput"> <Form.Field key="topicSubjectInput">
<Form.Input <Form.Input
name="topicSubjectInput" name="topicSubjectInput"
error={this.state.topicSubjectInputEmptySubmit} error={topicSubjectInputEmptySubmit}
type="text" type="text"
value={this.state.topicSubjectInput} value={topicSubjectInput}
placeholder="Subject" placeholder="Subject"
id="topicSubjectInput" id="topicSubjectInput"
onChange={this.handleInputChange} onChange={this.handleInputChange}
@ -103,10 +113,10 @@ class StartTopicContainer extends Component {
<TextArea <TextArea
key="topicMessageInput" key="topicMessageInput"
name="topicMessageInput" name="topicMessageInput"
className={this.state.topicMessageInputEmptySubmit className={topicMessageInputEmptySubmit
? 'form-textarea-required' ? 'form-textarea-required'
: ''} : ''}
value={this.state.topicMessageInput} value={topicMessageInput}
placeholder="Post" placeholder="Post"
id="topicMessageInput" id="topicMessageInput"
rows={5} 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 => ({ const mapStateToProps = state => ({
orbitDB: state.orbitDB, orbitDB: state.orbitDB,
user: state.user user: state.user

113
app/src/containers/TopicContainer.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -17,9 +18,11 @@ class TopicContainer extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { match, navigateTo } = props;
// Topic ID should be a positive integer // Topic ID should be a positive integer
if (!/^[0-9]+$/.test(this.props.match.params.topicId)) { if (!/^[0-9]+$/.test(match.params.topicId)) {
this.props.navigateTo('/404'); navigateTo('/404');
} }
this.getBlockchainData = this.getBlockchainData.bind(this); this.getBlockchainData = this.getBlockchainData.bind(this);
@ -29,46 +32,62 @@ class TopicContainer extends Component {
this.state = { this.state = {
pageStatus: 'initialized', pageStatus: 'initialized',
topicID: parseInt(this.props.match.params.topicId), topicID: parseInt(match.params.topicId),
topicSubject: null, topicSubject: null,
postFocus: this.props.match.params.postId postFocus: match.params.postId
&& /^[0-9]+$/.test(this.props.match.params.postId) && /^[0-9]+$/.test(match.params.postId)
? this.props.match.params.postId ? match.params.postId
: null, : null,
fetchTopicSubjectStatus: 'pending', fetchTopicSubjectStatus: 'pending',
posting: false posting: false
}; };
} }
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate() {
this.getBlockchainData();
}
componentWillUnmount() {
const { setNavBarTitle } = this.props;
setNavBarTitle('');
}
getBlockchainData() { getBlockchainData() {
if (this.state.pageStatus === 'initialized' const { pageStatus, topicID, fetchTopicSubjectStatus } = this.state;
&& this.props.drizzleStatus.initialized) { const { drizzleStatus, orbitDB, contracts } = this.props;
if (pageStatus === 'initialized'
&& drizzleStatus.initialized) {
this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall( this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(
this.state.topicID, topicID,
); );
this.setState({ this.setState({
pageStatus: 'loading' pageStatus: 'loading'
}); });
} }
if (this.state.pageStatus === 'loading' if (pageStatus === 'loading'
&& this.props.contracts[contract][getTopicMethod][this.dataKey]) { && contracts[contract][getTopicMethod][this.dataKey]) {
this.setState({ this.setState({
pageStatus: 'loaded' pageStatus: 'loaded'
}); });
if (this.props.orbitDB.orbitdb !== null) { if (orbitDB.orbitdb !== null) {
this.fetchTopicSubject( this.fetchTopicSubject(
this.props.contracts[contract][getTopicMethod][this.dataKey].value[0], contracts[contract][getTopicMethod][this.dataKey].value[0],
); );
this.setState({ this.setState({
fetchTopicSubjectStatus: 'fetching' fetchTopicSubjectStatus: 'fetching'
}); });
} }
} }
if (this.state.pageStatus === 'loaded' if (pageStatus === 'loaded'
&& this.state.fetchTopicSubjectStatus === 'pending' && fetchTopicSubjectStatus === 'pending'
&& this.props.orbitDB.orbitdb !== null) { && orbitDB.orbitdb !== null) {
this.fetchTopicSubject( this.fetchTopicSubject(
this.props.contracts[contract][getTopicMethod][this.dataKey].value[0], contracts[contract][getTopicMethod][this.dataKey].value[0],
); );
this.setState({ this.setState({
fetchTopicSubjectStatus: 'fetching' fetchTopicSubjectStatus: 'fetching'
@ -77,27 +96,30 @@ class TopicContainer extends Component {
} }
async fetchTopicSubject(orbitDBAddress) { async fetchTopicSubject(orbitDBAddress) {
const { topicID } = this.state;
const { contracts, user, orbitDB, setNavBarTitle } = this.props;
let orbitData; let orbitData;
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1] if (contracts[contract][getTopicMethod][this.dataKey].value[1]
=== this.props.user.address) { === user.address) {
orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID); orbitData = orbitDB.topicsDB.get(topicID);
} else { } else {
const fullAddress = `/orbitdb/${orbitDBAddress}/topics`; const fullAddress = `/orbitdb/${orbitDBAddress}/topics`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); const store = await orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
const localOrbitData = store.get(this.state.topicID); const localOrbitData = store.get(topicID);
if (localOrbitData) { if (localOrbitData) {
orbitData = localOrbitData; orbitData = localOrbitData;
} else { } else {
// Wait until we have received something from the network // Wait until we have received something from the network
store.events.on('replicated', () => { store.events.on('replicated', () => {
orbitData = store.get(this.state.topicID); orbitData = store.get(topicID);
}); });
} }
} }
this.props.setNavBarTitle(orbitData.subject); setNavBarTitle(orbitData.subject);
this.setState({ this.setState({
topicSubject: orbitData.subject, topicSubject: orbitData.subject,
fetchTopicSubjectStatus: 'fetched' fetchTopicSubjectStatus: 'fetched'
@ -120,30 +142,33 @@ class TopicContainer extends Component {
} }
render() { render() {
const { pageStatus, postFocus, topicID, topicSubject, posting } = this.state;
const { contracts, user } = this.props;
let topicContents; let topicContents;
if (this.state.pageStatus === 'loaded') { if (pageStatus === 'loaded') {
topicContents = ( topicContents = (
( (
<div> <div>
<PostList <PostList
postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]} postIDs={contracts[contract][getTopicMethod][this.dataKey].value[4]}
focusOnPost={this.state.postFocus focusOnPost={postFocus
? this.state.postFocus ? postFocus
: null} : null}
/> />
{this.state.posting {posting
&& ( && (
<NewPost <NewPost
topicID={this.state.topicID} topicID={topicID}
subject={this.state.topicSubject} subject={topicSubject}
postIndex={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4].length} postIndex={contracts[contract][getTopicMethod][this.dataKey].value[4].length}
onCancelClick={() => { this.togglePostingState(); }} onCancelClick={() => { this.togglePostingState(); }}
onPostCreated={() => { this.postCreated(); }} onPostCreated={() => { this.postCreated(); }}
/> />
) )
} }
<div className="posts-list-spacer" /> <div className="posts-list-spacer" />
{this.props.user.hasSignedUp && !this.state.posting {user.hasSignedUp && !posting
&& <FloatingButton onClick={this.togglePostingState} /> && <FloatingButton onClick={this.togglePostingState} />
} }
</div> </div>
@ -154,25 +179,23 @@ class TopicContainer extends Component {
return ( return (
<div className="fill"> <div className="fill">
{topicContents} {topicContents}
{!this.state.posting {!posting
&& <div className="bottom-overlay-pad" /> && <div className="bottom-overlay-pad" />
} }
</div> </div>
); );
} }
componentDidMount() {
this.getBlockchainData();
} }
componentDidUpdate() { TopicContainer.propTypes = {
this.getBlockchainData(); drizzleStatus: PropTypes.object.isRequired,
} orbitDB: PropTypes.object.isRequired,
setNavBarTitle: PropTypes.func.isRequired,
componentWillUnmount() { contracts: PropTypes.array.isRequired,
this.props.setNavBarTitle(''); user: PropTypes.object.isRequired,
} match: PropTypes.object.isRequired,
} navigateTo: PropTypes.func.isRequired
};
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: location => push(location), navigateTo: location => push(location),

60
app/src/containers/TransactionsMonitorContainer.js

@ -1,11 +1,12 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { Message } from 'semantic-ui-react'; import { Message } from 'semantic-ui-react';
class RightSideBar extends Component { class RightSideBar extends Component {
constructor(props, context) { constructor(props) {
super(props); super(props);
this.handleMessageClick = this.handleMessageClick.bind(this); this.handleMessageClick = this.handleMessageClick.bind(this);
@ -17,33 +18,35 @@ class RightSideBar extends Component {
} }
handleMessageClick(index) { handleMessageClick(index) {
const transactionHash = this.props.transactionStack[index]; const { transactionStack, history, transactions } = this.props;
if (this.props.transactions[transactionHash]) {
if (this.props.transactions[transactionHash].status === 'error') { const transactionHash = transactionStack[index];
if (transactions[transactionHash]) {
if (transactions[transactionHash].status === 'error') {
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
} else if (this.props.transactions[transactionHash].receipt } else if (transactions[transactionHash].receipt
&& this.props.transactions[transactionHash].receipt.events) { && transactions[transactionHash].receipt.events) {
switch (Object.keys( switch (Object.keys(
this.props.transactions[transactionHash].receipt.events, transactions[transactionHash].receipt.events,
)[0]) { )[0]) {
case 'UserSignedUp': case 'UserSignedUp':
this.props.history.push('/profile'); history.push('/profile');
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
break; break;
case 'UsernameUpdated': case 'UsernameUpdated':
this.props.history.push('/profile'); history.push('/profile');
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
break; break;
case 'TopicCreated': case 'TopicCreated':
this.props.history.push(`/topic/${ history.push(`/topic/${
this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID}`); transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID}`);
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
break; break;
case 'PostCreated': case 'PostCreated':
this.props.history.push(`/topic/${ history.push(`/topic/${
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID 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); this.handleMessageDismiss(null, index);
break; break;
default: default:
@ -59,7 +62,9 @@ class RightSideBar extends Component {
event.stopPropagation(); event.stopPropagation();
} }
const isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice(); const { isTransactionMessageDismissed } = this.state;
const isTransactionMessageDismissedShallowCopy = isTransactionMessageDismissed.slice();
isTransactionMessageDismissedShallowCopy[messageIndex] = true; isTransactionMessageDismissedShallowCopy[messageIndex] = true;
this.setState({ this.setState({
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy
@ -67,13 +72,16 @@ class RightSideBar extends Component {
} }
render() { render() {
if (this.props.transactionStack.length === 0) { const { isTransactionMessageDismissed } = this.state;
const { transactionStack, transactions } = this.props;
if (transactionStack.length === 0) {
return null; return null;
} }
const transactionMessages = this.props.transactionStack.map( const transactionMessages = transactionStack.map(
(transaction, index) => { (transaction, index) => {
if (this.state.isTransactionMessageDismissed[index]) { if (isTransactionMessageDismissed[index]) {
return null; return null;
} }
@ -82,20 +90,20 @@ class RightSideBar extends Component {
message.push( message.push(
'New transaction has been queued and is waiting your confirmation.', '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(<br key="confirmed" />);
message.push('- transaction confirmed'); message.push('- transaction confirmed');
} }
if (this.props.transactions[transaction] if (transactions[transaction]
&& this.props.transactions[transaction].status === 'success') { && transactions[transaction].status === 'success') {
/* Transaction completed successfully */ /* Transaction completed successfully */
message.push(<br key="mined" />); message.push(<br key="mined" />);
message.push('- transaction mined'); message.push('- transaction mined');
color = 'green'; color = 'green';
message.push(<br key="success" />); message.push(<br key="success" />);
message.push('- transaction completed successfully'); message.push('- transaction completed successfully');
} else if (this.props.transactions[transaction] } else if (transactions[transaction]
&& this.props.transactions[transaction].status === 'error') { && transactions[transaction].status === 'error') {
/* Transaction failed to complete */ /* Transaction failed to complete */
message.push(<br key="mined" />); message.push(<br key="mined" />);
message.push('- transaction 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 => ({ const mapStateToProps = state => ({
transactions: state.transactions, transactions: state.transactions,
transactionStack: state.transactionStack transactionStack: state.transactionStack

68
app/src/containers/UsernameFormContainer.js

@ -1,4 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Button, Dimmer, Form, Header, Loader, Message } from 'semantic-ui-react'; import { Button, Dimmer, Form, Header, Loader, Message } from 'semantic-ui-react';
@ -48,16 +49,18 @@ class UsernameFormContainer extends Component {
} }
handleSubmit() { handleSubmit() {
if (this.state.usernameInput === '') { const { usernameInput, error } = this.state;
if (usernameInput === '') {
this.setState({ this.setState({
error: true, error: true,
errorHeader: 'Data Incomplete', errorHeader: 'Data Incomplete',
errorMessage: 'You need to provide a username' 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 // Makes sure current input username has been checked for availability
if (this.checkedUsernames.some( if (this.checkedUsernames.some(
e => e.usernameChecked === this.state.usernameInput, e => e.usernameChecked === usernameInput,
)) { )) {
this.completeAction(); this.completeAction();
} }
@ -65,8 +68,11 @@ class UsernameFormContainer extends Component {
} }
async completeAction() { async completeAction() {
if (this.props.user.hasSignedUp) { const { usernameInput } = this.state;
this.props.dispatch(updateUsername(...[this.state.usernameInput], null)); const { user, dispatch, account } = this.props;
if (user.hasSignedUp) {
dispatch(updateUsername(...[usernameInput], null));
} else { } else {
this.setState({ this.setState({
signingUp: true signingUp: true
@ -74,7 +80,7 @@ class UsernameFormContainer extends Component {
const orbitdbInfo = await createDatabases(); const orbitdbInfo = await createDatabases();
this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend( this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend(
...[ ...[
this.state.usernameInput, usernameInput,
orbitdbInfo.identityId, orbitdbInfo.identityId,
orbitdbInfo.identityPublicKey, orbitdbInfo.identityPublicKey,
orbitdbInfo.identityPrivateKey, orbitdbInfo.identityPrivateKey,
@ -84,7 +90,7 @@ class UsernameFormContainer extends Component {
orbitdbInfo.topicsDB, orbitdbInfo.topicsDB,
orbitdbInfo.postsDB orbitdbInfo.postsDB
], { ], {
from: this.props.account from: account
}, },
); );
} }
@ -94,18 +100,21 @@ class UsernameFormContainer extends Component {
} }
componentDidUpdate() { componentDidUpdate() {
if (this.state.signingUp) { const { signingUp, usernameInput, error } = this.state;
const txHash = this.props.transactionStack[this.stackId]; const { transactionStack, transactions, contracts } = this.props;
if (signingUp) {
const txHash = transactionStack[this.stackId];
if (txHash if (txHash
&& this.props.transactions[txHash] && transactions[txHash]
&& this.props.transactions[txHash].status === 'error') { && transactions[txHash].status === 'error') {
this.setState({ this.setState({
signingUp: false signingUp: false
}); });
} }
} else { } else {
const temp = Object.values( const temp = Object.values(
this.props.contracts[contract][checkUsernameTakenMethod], contracts[contract][checkUsernameTakenMethod],
); );
this.checkedUsernames = temp.map(checked => ({ this.checkedUsernames = temp.map(checked => ({
usernameChecked: checked.args[0], usernameChecked: checked.args[0],
@ -114,8 +123,8 @@ class UsernameFormContainer extends Component {
if (this.checkedUsernames.length > 0) { if (this.checkedUsernames.length > 0) {
this.checkedUsernames.forEach((checked) => { this.checkedUsernames.forEach((checked) => {
if (checked.usernameChecked === this.state.usernameInput if (checked.usernameChecked === usernameInput
&& checked.isTaken && !this.state.error) { && checked.isTaken && !error) {
this.setState({ this.setState({
error: true, error: true,
errorHeader: 'Data disapproved', errorHeader: 'Data disapproved',
@ -128,14 +137,15 @@ class UsernameFormContainer extends Component {
} }
render() { render() {
const { hasSignedUp } = this.props.user; const { error, usernameInput, errorHeader, errorMessage, signingUp } = this.state;
const { user } = this.props;
if (hasSignedUp !== null) { if (user.hasSignedUp !== null) {
const buttonText = hasSignedUp ? 'Update' : 'Sign Up'; const buttonText = user.hasSignedUp ? 'Update' : 'Sign Up';
const placeholderText = hasSignedUp const placeholderText = user.hasSignedUp
? this.props.user.username ? user.username
: 'Username'; : 'Username';
const withError = this.state.error && { const withError = error && {
error: true error: true
}; };
@ -158,18 +168,18 @@ class UsernameFormContainer extends Component {
<Form.Input <Form.Input
placeholder={placeholderText} placeholder={placeholderText}
name="usernameInput" name="usernameInput"
value={this.state.usernameInput} value={usernameInput}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
</Form.Field> </Form.Field>
<Message <Message
error error
header={this.state.errorHeader} header={errorHeader}
content={this.state.errorMessage} content={errorMessage}
/> />
<Button type="submit">{buttonText}</Button> <Button type="submit">{buttonText}</Button>
</Form> </Form>
<Dimmer active={this.state.signingUp} page> <Dimmer active={signingUp} page>
<Header as="h2" inverted> <Header as="h2" inverted>
<Loader size="large"> <Loader size="large">
Magic elves are processing your noble 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 => ({ const mapStateToProps = state => ({
account: state.accounts[0], account: state.accounts[0],
contracts: state.contracts, 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); const transactionsHistory = Object.create(null);
function* initTransaction(action) { 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), ...(action.transactionDescriptor.params),
); );

2
package.json

@ -15,7 +15,7 @@
"openzeppelin-solidity": "^2.1.2" "openzeppelin-solidity": "^2.1.2"
}, },
"devDependencies": { "devDependencies": {
"eslint": "5.15.1", "eslint": "5.12.0",
"eslint-config-airbnb": "17.1.0", "eslint-config-airbnb": "17.1.0",
"eslint-plugin-import": "2.16.0", "eslint-plugin-import": "2.16.0",
"eslint-plugin-jsx-a11y": "6.2.1", "eslint-plugin-jsx-a11y": "6.2.1",

Loading…
Cancel
Save