Browse Source

ESLint init

develop
Ezerous 6 years ago
parent
commit
c5bd23377b
  1. 15
      .eslintrc.json
  2. 1
      .eslintrignore
  3. 3
      app/.eslintrc.json
  4. 1
      app/.eslintrignore
  5. 5
      app/package.json
  6. 12
      app/src/assets/css/App.css
  7. 2
      app/src/assets/css/sign-up-container.css
  8. 2
      app/src/assets/css/topic-container.css
  9. 11534
      app/src/assets/fonts/fontawesome-free-5.7.2/all.js
  10. 12
      app/src/components/FloatingButton.js
  11. 15
      app/src/components/LoadingSpinner.js
  12. 127
      app/src/components/NewPost.js
  13. 27
      app/src/components/NewTopicPreview.js
  14. 13
      app/src/components/NotFound.js
  15. 211
      app/src/components/Post.js
  16. 78
      app/src/components/PostList.js
  17. 80
      app/src/components/ProfileInformation.js
  18. 121
      app/src/components/Topic.js
  19. 44
      app/src/components/TopicList.js
  20. 4
      app/src/config/drizzleOptions.js
  21. 4
      app/src/config/ipfsOptions.js
  22. 87
      app/src/containers/BoardContainer.js
  23. 17
      app/src/containers/CoreLayoutContainer.js
  24. 8
      app/src/containers/HomeContainer.js
  25. 42
      app/src/containers/NavBarContainer.js
  26. 133
      app/src/containers/ProfileContainer.js
  27. 31
      app/src/containers/SignUpContainer.js
  28. 91
      app/src/containers/StartTopicContainer.js
  29. 121
      app/src/containers/TopicContainer.js
  30. 105
      app/src/containers/TransactionsMonitorContainer.js
  31. 123
      app/src/containers/UsernameFormContainer.js
  32. 16
      app/src/helpers/EpochTimeConverter.js
  33. 14
      app/src/index.js
  34. 8
      app/src/redux/actions/orbitActions.js
  35. 16
      app/src/redux/actions/transactionsActions.js
  36. 4
      app/src/redux/actions/userInterfaceActions.js
  37. 7
      app/src/redux/reducers/orbitReducer.js
  38. 6
      app/src/redux/reducers/rootReducer.js
  39. 10
      app/src/redux/reducers/userReducer.js
  40. 35
      app/src/redux/sagas/drizzleUtilsSaga.js
  41. 60
      app/src/redux/sagas/orbitSaga.js
  42. 21
      app/src/redux/sagas/rootSaga.js
  43. 82
      app/src/redux/sagas/transactionsSaga.js
  44. 54
      app/src/redux/sagas/userSaga.js
  45. 19
      app/src/redux/store.js
  46. 25
      app/src/router/PrivateRoute.js
  47. 27
      app/src/router/routes.js
  48. 34
      app/src/utils/drizzleUtils.js
  49. 44
      app/src/utils/orbitUtils.js
  50. 41
      app/src/utils/serviceWorker.js
  51. 7
      package.json
  52. 14
      truffle-config.js

15
.eslintrc.json

@ -0,0 +1,15 @@
{
"rules": {
"comma-dangle": ["error", "never"],
"no-console": "off",
"no-unused-vars": "warn",
"object-curly-newline": ["error", {
"ObjectExpression": "always",
"ObjectPattern": { "multiline": true },
"ImportDeclaration": "never",
"ExportDeclaration": "never"
}],
"object-curly-spacing": ["error", "always"]
},
"extends": "airbnb"
}

1
.eslintrignore

@ -0,0 +1 @@
# node_modules in the project root is ignored by default

3
app/.eslintrc.json

@ -0,0 +1,3 @@
{
"extends": "plugin:react/recommended"
}

1
app/.eslintrignore

@ -0,0 +1 @@
node_modules/*

5
app/package.json

@ -25,7 +25,7 @@
"react-user-avatar": "1.10.0", "react-user-avatar": "1.10.0",
"redux": "4.0.1", "redux": "4.0.1",
"redux-saga": "0.16.2", "redux-saga": "0.16.2",
"semantic-ui-react": "0.85.0", "semantic-ui-react": "0.86.0",
"uuid": "3.3.2", "uuid": "3.3.2",
"web3": "1.0.0-beta.48" "web3": "1.0.0-beta.48"
}, },
@ -35,9 +35,6 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",
"not dead", "not dead",

12
app/src/assets/css/App.css

@ -130,11 +130,11 @@ hr {
} }
*:focus { *:focus {
outline:none !important outline: none !important
} }
a { a {
color:inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
@ -167,18 +167,18 @@ a {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0,0,0,0.5); background-color: rgba(0, 0, 0, 0.5);
z-index: 2; z-index: 2;
} }
#overlay-content{ #overlay-content {
position: absolute; position: absolute;
text-align: center; text-align: center;
top: 50%; top: 50%;
left: 50%; left: 50%;
color: white; color: white;
transform: translate(-50%,-50%); transform: translate(-50%, -50%);
-ms-transform: translate(-50%,-50%); -ms-transform: translate(-50%, -50%);
} }
.fill { .fill {

2
app/src/assets/css/sign-up-container.css

@ -7,6 +7,6 @@
justify-content: center; justify-content: center;
} }
.sign-up-container>div { .sign-up-container > div {
margin: auto; margin: auto;
} }

2
app/src/assets/css/topic-container.css

@ -40,7 +40,7 @@
padding: 7px; padding: 7px;
} }
.post-content a{ .post-content a {
margin-top: 10px; margin-top: 10px;
color: #039be5; color: #039be5;
} }

11534
app/src/assets/fonts/fontawesome-free-5.7.2/all.js

File diff suppressed because one or more lines are too long

12
app/src/components/FloatingButton.js

@ -1,14 +1,12 @@
import React from 'react'; import React from 'react';
import { Button, Icon } from 'semantic-ui-react' import { Button, Icon } from 'semantic-ui-react';
const FloatingButton = (props) => { const FloatingButton = props => (
return (
<div className="action-button" onClick={props.onClick}> <div className="action-button" onClick={props.onClick}>
<Button icon color='teal' size='large'> <Button icon color="teal" size="large">
<Icon name='add'/> <Icon name="add" />
</Button> </Button>
</div> </div>
); );
};
export default FloatingButton; export default FloatingButton;

15
app/src/components/LoadingSpinner.js

@ -1,16 +1,17 @@
import React from 'react'; import React from 'react';
const LoadingSpinner = (props) => { const LoadingSpinner = props => (
return(
<div className="vertical-center-children"> <div className="vertical-center-children">
<div className={"center-in-parent " + (props.className ? props.className : "")} <div
style={props.style ? props.style : []}> className={`center-in-parent ${
props.className ? props.className : ''}`}
style={props.style ? props.style : []}
>
<p> <p>
<i className="fas fa-spinner fa-3x fa-spin"></i> <i className="fas fa-spinner fa-3x fa-spin" />
</p> </p>
</div> </div>
</div> </div>
); );
}
export default LoadingSpinner; export default LoadingSpinner;

127
app/src/components/NewPost.js

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Grid, Form, TextArea, Button, Icon, Divider } from 'semantic-ui-react' import { Button, Divider, Form, Grid, Icon, TextArea } from 'semantic-ui-react';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
@ -20,7 +20,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: this.props.subject ? this.props.subject : '',
postContentInput: '', postContentInput: '',
postSubjectInputEmptySubmit: false, postSubjectInputEmptySubmit: false,
postContentInputEmptySubmit: false, postContentInputEmptySubmit: false,
@ -30,7 +30,8 @@ class NewPost extends Component {
} }
async validateAndPost() { async validateAndPost() {
if (this.state.postSubjectInput === '' || this.state.postContentInput === ''){ if (this.state.postSubjectInput === '' || this.state.postContentInput
=== '') {
this.setState({ this.setState({
postSubjectInputEmptySubmit: this.state.postSubjectInput === '', postSubjectInputEmptySubmit: this.state.postSubjectInput === '',
postContentInputEmptySubmit: this.state.postContentInput === '' postContentInputEmptySubmit: this.state.postContentInput === ''
@ -43,14 +44,15 @@ class NewPost extends Component {
{ {
postSubject: this.state.postSubjectInput, postSubject: this.state.postSubjectInput,
postMessage: this.state.postContentInput postMessage: this.state.postContentInput
} }),
)
); );
this.props.onPostCreated(); this.props.onPostCreated();
} }
handleInputChange(event) { handleInputChange(event) {
this.setState({[event.target.name]: event.target.value}); this.setState({
[event.target.name]: event.target.value
});
} }
handlePreviewToggle() { handlePreviewToggle() {
@ -62,19 +64,22 @@ class NewPost extends Component {
getDate() { getDate() {
const currentdate = new Date(); const currentdate = new Date();
return ((currentdate.getMonth() + 1) + " " return (`${currentdate.getMonth() + 1} ${
+ currentdate.getDate() + ", " currentdate.getDate()}, ${
+ currentdate.getFullYear() + ", " currentdate.getFullYear()}, ${
+ currentdate.getHours() + ":" currentdate.getHours()}:${
+ currentdate.getMinutes() + ":" currentdate.getMinutes()}:${
+ currentdate.getSeconds()); currentdate.getSeconds()}`);
} }
render() { render() {
return ( return (
<div className="post" ref={this.newPostOuterRef}> <div className="post" ref={this.newPostOuterRef}>
<Divider horizontal> <Divider horizontal>
<span className="grey-text">#{this.props.postIndex}</span> <span className="grey-text">
#
{this.props.postIndex}
</span>
</Divider> </Divider>
<Grid> <Grid>
<Grid.Row columns={16} stretched> <Grid.Row columns={16} stretched>
@ -91,62 +96,94 @@ class NewPost extends Component {
<div className="stretch-space-between"> <div className="stretch-space-between">
<span><strong>{this.props.user.username}</strong></span> <span><strong>{this.props.user.username}</strong></span>
<span className="grey-text"> <span className="grey-text">
{this.state.previewEnabled && {this.state.previewEnabled
<TimeAgo date={this.state.previewDate}/> && <TimeAgo date={this.state.previewDate} />
} }
</span> </span>
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
<span><strong> <span>
{this.state.previewEnabled && <strong>
("Subject: " + this.state.postSubjectInput) {this.state.previewEnabled
&& (`Subject: ${
this.state.postSubjectInput}`)
} }
</strong></span> </strong>
</span>
</div> </div>
<div className="post-content"> <div className="post-content">
<div style={{display: this.state.previewEnabled ? "block" : "none"}}> <div style={{
<ReactMarkdown source={this.state.postContentInput} display: this.state.previewEnabled
className="markdown-preview" /> ? 'block'
: 'none'
}}
>
<ReactMarkdown
source={this.state.postContentInput}
className="markdown-preview"
/>
</div> </div>
<Form className="topic-form"> <Form className="topic-form">
<Form.Input key={"postSubjectInput"} <Form.Input
style={{display: this.state.previewEnabled ? "none" : ""}} key="postSubjectInput"
name={"postSubjectInput"} style={{
display: this.state.previewEnabled
? 'none'
: ''
}}
name="postSubjectInput"
error={this.state.postSubjectInputEmptySubmit} error={this.state.postSubjectInputEmptySubmit}
type="text" type="text"
value={this.state.postSubjectInput} value={this.state.postSubjectInput}
placeholder="Subject" placeholder="Subject"
id="postSubjectInput" id="postSubjectInput"
onChange={this.handleInputChange} /> onChange={this.handleInputChange}
<TextArea key={"postContentInput"} />
style={{display: this.state.previewEnabled ? "none" : ""}} <TextArea
name={"postContentInput"} key="postContentInput"
className={this.state.postContentInputEmptySubmit ? "form-textarea-required" : ""} style={{
display: this.state.previewEnabled
? 'none'
: ''
}}
name="postContentInput"
className={this.state.postContentInputEmptySubmit
? 'form-textarea-required'
: ''}
value={this.state.postContentInput} value={this.state.postContentInput}
placeholder="Post" placeholder="Post"
id="postContentInput" id="postContentInput"
onChange={this.handleInputChange} onChange={this.handleInputChange}
rows={4} autoHeight /> rows={4}
<br/><br/> autoHeight
/>
<br />
<br />
<Button.Group> <Button.Group>
<Button key="submit" <Button
key="submit"
type="button" type="button"
onClick={this.validateAndPost} onClick={this.validateAndPost}
color='teal' color="teal"
animated> animated
>
<Button.Content visible>Post</Button.Content> <Button.Content visible>Post</Button.Content>
<Button.Content hidden> <Button.Content hidden>
<Icon name='reply' /> <Icon name="reply" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button type="button" <Button
type="button"
onClick={this.handlePreviewToggle} onClick={this.handlePreviewToggle}
color='yellow'> color="yellow"
{this.state.previewEnabled ? "Edit" : "Preview"} >
{this.state.previewEnabled ? 'Edit' : 'Preview'}
</Button> </Button>
<Button type="button" <Button
type="button"
onClick={this.props.onCancelClick} onClick={this.props.onCancelClick}
color='red'> color="red"
>
Cancel Cancel
</Button> </Button>
</Button.Group> </Button.Group>
@ -160,16 +197,14 @@ class NewPost extends Component {
); );
} }
componentDidMount(){ componentDidMount() {
this.newPostOuterRef.current.scrollIntoView(true); this.newPostOuterRef.current.scrollIntoView(true);
} }
} }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
orbitDB: state.orbitDB, orbitDB: state.orbitDB,
user: state.user user: state.user
} });
};
export default connect(mapStateToProps)(NewPost); export default connect(mapStateToProps)(NewPost);

27
app/src/components/NewTopicPreview.js

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Grid, Divider } from 'semantic-ui-react' import { Divider, Grid } from 'semantic-ui-react';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
@ -12,7 +12,7 @@ class Post extends Component {
super(props); super(props);
} }
render(){ render() {
return ( return (
<div className="post"> <div className="post">
<Divider horizontal> <Divider horizontal>
@ -25,7 +25,8 @@ class Post extends Component {
size="52" size="52"
className="inline" className="inline"
src={this.props.user.avatarUrl} src={this.props.user.avatarUrl}
name={this.props.user.username}/> name={this.props.user.username}
/>
</Grid.Column> </Grid.Column>
<Grid.Column width={15}> <Grid.Column width={15}>
<div className=""> <div className="">
@ -36,13 +37,17 @@ class Post extends Component {
</strong> </strong>
</span> </span>
<span className="grey-text"> <span className="grey-text">
<TimeAgo date={this.props.date}/> <TimeAgo date={this.props.date} />
</span> </span>
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
<span><strong> <span>
Subject: {this.props.subject} <strong>
</strong></span> Subject:
{' '}
{this.props.subject}
</strong>
</span>
</div> </div>
<div className="post-content"> <div className="post-content">
<ReactMarkdown source={this.props.content} /> <ReactMarkdown source={this.props.content} />
@ -54,12 +59,10 @@ class Post extends Component {
</div> </div>
); );
} }
}; }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
user: state.user user: state.user
} });
};
export default connect(mapStateToProps)(Post); export default connect(mapStateToProps)(Post);

13
app/src/components/NotFound.js

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import pageNotFound from '../assets/images/PageNotFound.jpg'; import pageNotFound from '../assets/images/PageNotFound.jpg';
const NotFound = () => { const NotFound = () => (
return ( <div style={{
<div style={{textAlign: "center"}}> textAlign: 'center'
<img src={pageNotFound} alt="Page not found!"/> }}
>
<img src={pageNotFound} alt="Page not found!" />
</div> </div>
); );
};
export default NotFound; export default NotFound;

211
app/src/components/Post.js

@ -1,17 +1,16 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
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';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ContentLoader from "react-content-loader" import ContentLoader from 'react-content-loader';
import { Transition } from 'semantic-ui-react' import { Button, Divider, Grid, Icon, Label, Transition } from 'semantic-ui-react';
import { Grid, Divider, Button, Icon, Label } from 'semantic-ui-react'
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import epochTimeConverter from '../helpers/EpochTimeConverter';
class Post extends Component { class Post extends Component {
constructor(props) { constructor(props) {
@ -19,7 +18,7 @@ class Post extends Component {
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 (props.getFocus) {
this.postRef = React.createRef(); this.postRef = React.createRef();
} }
@ -29,14 +28,16 @@ class Post extends Component {
postSubject: '', postSubject: '',
readyForAnimation: false, readyForAnimation: false,
animateOnToggle: true animateOnToggle: true
} };
} }
getBlockchainData() { getBlockchainData() {
if (this.props.postData && if (this.props.postData
this.props.orbitDB.orbitdb && && this.props.orbitDB.orbitdb
this.state.fetchPostDataStatus === "pending") { && this.state.fetchPostDataStatus === 'pending') {
this.setState({ fetchPostDataStatus: 'fetching' }); this.setState({
fetchPostDataStatus: 'fetching'
});
this.fetchPost(this.props.postID); this.fetchPost(this.props.postID);
} }
} }
@ -46,18 +47,18 @@ class Post extends Component {
if (this.props.postData.value[1] === this.props.user.address) { if (this.props.postData.value[1] === this.props.user.address) {
orbitPostData = this.props.orbitDB.postsDB.get(postID); orbitPostData = this.props.orbitDB.postsDB.get(postID);
} else { } else {
const fullAddress = "/orbitdb/" + this.props.postData.value[0] + "/posts"; const fullAddress = `/orbitdb/${this.props.postData.value[0]}/posts`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
let localOrbitData = store.get(postID); const localOrbitData = store.get(postID);
if (localOrbitData) { if (localOrbitData) {
orbitPostData = localOrbitData; orbitPostData = 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', () => {
orbitPostData = store.get(postID); orbitPostData = store.get(postID);
}) });
} }
} }
@ -69,65 +70,111 @@ class Post extends Component {
}); });
} }
render(){ render() {
let avatarView = (this.props.postData const avatarView = (this.props.postData
? <UserAvatar ? (
<UserAvatar
size="52" size="52"
className="inline" className="inline"
src={this.props.avatarUrl} src={this.props.avatarUrl}
name={this.props.postData.value[2]}/> name={this.props.postData.value[2]}
: <div className="user-avatar"> />
<ContentLoader height={52} width={52} speed={2} )
primaryColor="#b2e8e6" secondaryColor="#00b5ad"> : (
<div className="user-avatar">
<ContentLoader
height={52}
width={52}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<circle cx="26" cy="26" r="26" /> <circle cx="26" cy="26" r="26" />
</ContentLoader> </ContentLoader>
</div> </div>
)
); );
return ( return (
<Transition animation='tada' duration={500} visible={this.state.animateOnToggle}> <Transition
animation="tada"
duration={500}
visible={this.state.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">#{this.props.postIndex}</span> <span className="grey-text">
#
{this.props.postIndex}
</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 {this.props.postData !== null
?<Link to={"/profile/" + this.props.postData.value[1] ? (
+ "/" + this.props.postData.value[2]} <Link
onClick={(event) => {event.stopPropagation()}}> to={`/profile/${this.props.postData.value[1]
}/${this.props.postData.value[2]}`}
onClick={(event) => { event.stopPropagation(); }}
>
{avatarView} {avatarView}
</Link> </Link>
:avatarView )
: avatarView
} }
</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 className={this.props.postData !== null ? "" : "grey-text"}> <span className={this.props.postData
!== null ? '' : 'grey-text'}
>
<strong> <strong>
{this.props.postData !== null {this.props.postData !== null
?this.props.postData.value[2] ? this.props.postData.value[2]
:"Username" : 'Username'
} }
</strong> </strong>
</span> </span>
<span className="grey-text"> <span className="grey-text">
{this.props.postData !== null && {this.props.postData !== null
<TimeAgo date={epochTimeConverter(this.props.postData.value[3])}/> && (
<TimeAgo date={epochTimeConverter(
this.props.postData.value[3],
)}
/>
)
} }
</span> </span>
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
<span className={this.state.postSubject === '' ? "" : "grey-text"}> <span
className={this.state.postSubject
=== '' ? '' : 'grey-text'}
>
<strong> <strong>
{this.state.postSubject === '' {this.state.postSubject === ''
? <ContentLoader height={5.8} width={300} speed={2} ? (
primaryColor="#b2e8e6" secondaryColor="#00b5ad" > <ContentLoader
<rect x="0" y="0" rx="3" ry="3" width="75" height="5.5" /> height={5.8}
width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect
x="0"
y="0"
rx="3"
ry="3"
width="75"
height="5.5"
/>
</ContentLoader> </ContentLoader>
: 'Subject: ' + this.state.postSubject )
: `Subject: ${
this.state.postSubject}`
} }
</strong> </strong>
</span> </span>
@ -135,11 +182,32 @@ class Post extends Component {
<div className="post-content"> <div className="post-content">
{this.state.postContent !== '' {this.state.postContent !== ''
? <ReactMarkdown source={this.state.postContent} /> ? <ReactMarkdown source={this.state.postContent} />
: <ContentLoader height={11.2} width={300} speed={2} : (
primaryColor="#b2e8e6" secondaryColor="#00b5ad" > <ContentLoader
<rect x="0" y="0" rx="3" ry="3" width="180" height="4.0" /> height={11.2}
<rect x="0" y="6.5" rx="3" ry="3" width="140" height="4.0" /> width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect
x="0"
y="0"
rx="3"
ry="3"
width="180"
height="4.0"
/>
<rect
x="0"
y="6.5"
rx="3"
ry="3"
width="140"
height="4.0"
/>
</ContentLoader> </ContentLoader>
)
} }
</div> </div>
</div> </div>
@ -147,20 +215,31 @@ class Post extends Component {
</Grid.Row> </Grid.Row>
<Grid.Row> <Grid.Row>
<Grid.Column floated="right" textAlign="right"> <Grid.Column floated="right" textAlign="right">
<Button icon size='mini' style={{marginRight: "0px"}}> <Button
<Icon name='chevron up' /> icon
size="mini"
style={{
marginRight: '0px'
}}
>
<Icon name="chevron up" />
</Button> </Button>
<Label color="teal">8000</Label> <Label color="teal">8000</Label>
<Button icon size='mini'> <Button icon size="mini">
<Icon name='chevron down' /> <Icon name="chevron down" />
</Button> </Button>
<Button icon size='mini' <Button
icon
size="mini"
onClick={this.props.postData onClick={this.props.postData
? () => { this.props.navigateTo("/topic/" ? () => {
+ this.props.postData.value[4] + "/" this.props.navigateTo(`/topic/${
+ this.props.postID)} this.props.postData.value[4]}/${
: () => {}}> this.props.postID}`);
<Icon name='linkify' /> }
: () => {}}
>
<Icon name="linkify" />
</Button> </Button>
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
@ -174,31 +253,37 @@ class Post extends Component {
this.getBlockchainData(); this.getBlockchainData();
} }
componentDidUpdate(){ componentDidUpdate() {
this.getBlockchainData(); this.getBlockchainData();
if (this.state.readyForAnimation){ if (this.state.readyForAnimation) {
if (this.postRef){ if (this.postRef) {
setTimeout(() => { setTimeout(() => {
this.postRef.current.scrollIntoView({ block: 'start', behavior: 'smooth' }); this.postRef.current.scrollIntoView(
{
block: 'start', behavior: 'smooth'
},
);
setTimeout(() => { setTimeout(() => {
this.setState({ animateOnToggle: false }); this.setState({
animateOnToggle: false
});
}, 300); }, 300);
}, 100); }, 100);
this.setState({ readyForAnimation: false }); this.setState({
readyForAnimation: false
});
} }
} }
} }
}; }
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location) navigateTo: location => push(location)
}, dispatch); }, dispatch);
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
user: state.user, user: state.user,
orbitDB: state.orbit orbitDB: state.orbit
} });
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post)); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post));

78
app/src/components/PostList.js

@ -4,8 +4,8 @@ import { drizzle } from '../index';
import Post from './Post'; import Post from './Post';
const contract = "Forum"; const contract = 'Forum';
const getPostMethod = "getPost"; const getPostMethod = 'getPost';
class PostList extends Component { class PostList extends Component {
constructor(props) { constructor(props) {
@ -15,47 +15,29 @@ class PostList extends Component {
this.state = { this.state = {
dataKeys: [] dataKeys: []
} };
}
getBlockchainData(){
if (this.props.drizzleStatus['initialized']){
let dataKeysShallowCopy = this.state.dataKeys.slice();
let fetchingNewData = false;
this.props.postIDs.forEach( postID => {
if (!this.state.dataKeys[postID]) {
dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(postID);
fetchingNewData = true;
}
})
if (fetchingNewData){
this.setState({
dataKeys: dataKeysShallowCopy
});
}
}
} }
render() { render() {
const posts = this.props.postIDs.map((postID, index) => { const posts = this.props.postIDs.map((postID, index) => (
return (<Post <Post
postData={(this.state.dataKeys[postID] && this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]]) postData={(this.state.dataKeys[postID]
&& this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]])
? this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]] ? this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]]
: null} : null}
avatarUrl={""} avatarUrl=""
postIndex={index} postIndex={index}
postID={postID} postID={postID}
getFocus={this.props.focusOnPost === postID ? true : false} getFocus={this.props.focusOnPost === postID}
key={postID} />) key={postID}
}); />
));
return ( return (
<div> <div>
{this.props.recentToTheTop {this.props.recentToTheTop
?posts.slice(0).reverse() ? posts.slice(0).reverse()
:posts : posts
} }
</div> </div>
); );
@ -65,16 +47,36 @@ class PostList extends Component {
this.getBlockchainData(); this.getBlockchainData();
} }
componentDidUpdate(){ componentDidUpdate() {
this.getBlockchainData(); this.getBlockchainData();
} }
};
const mapStateToProps = state => { getBlockchainData() {
return { if (this.props.drizzleStatus.initialized) {
const dataKeysShallowCopy = this.state.dataKeys.slice();
let fetchingNewData = false;
this.props.postIDs.forEach((postID) => {
if (!this.state.dataKeys[postID]) {
dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(
postID,
);
fetchingNewData = true;
}
});
if (fetchingNewData) {
this.setState({
dataKeys: dataKeysShallowCopy
});
}
}
}
}
const mapStateToProps = state => ({
contracts: state.contracts, contracts: state.contracts,
drizzleStatus: state.drizzleStatus drizzleStatus: state.drizzleStatus
} });
};
export default connect(mapStateToProps)(PostList); export default connect(mapStateToProps)(PostList);

80
app/src/components/ProfileInformation.js

@ -1,19 +1,20 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import UserAvatar from 'react-user-avatar';
import { drizzle } from '../index'; import { drizzle } from '../index';
import UserAvatar from 'react-user-avatar';
import epochTimeConverter from '../helpers/EpochTimeConverter'; import epochTimeConverter from '../helpers/EpochTimeConverter';
import UsernameFormContainer from '../containers/UsernameFormContainer'; import UsernameFormContainer from '../containers/UsernameFormContainer';
const callsInfo = [{ const callsInfo = [
{
contract: 'Forum', contract: 'Forum',
method: 'getUserDateOfRegister' method: 'getUserDateOfRegister'
},{ }, {
contract: 'Forum', contract: 'Forum',
method: 'getOrbitDBId' method: 'getOrbitDBId'
}] }];
class ProfileInformation extends Component { class ProfileInformation extends Component {
constructor(props) { constructor(props) {
@ -29,41 +30,49 @@ class ProfileInformation extends Component {
}; };
} }
getBlockchainData(){ getBlockchainData() {
if (this.state.pageStatus === 'initialized' && if (this.state.pageStatus === 'initialized'
this.props.drizzleStatus['initialized']) { && this.props.drizzleStatus.initialized) {
callsInfo.forEach((call, index) => { callsInfo.forEach((call, index) => {
this.dataKey[index] = drizzle.contracts[call.contract] this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall(
.methods[call.method].cacheCall(this.props.address); this.props.address,
}) );
this.setState({ pageStatus: 'loading' }); });
this.setState({
pageStatus: 'loading'
});
} }
if (this.state.pageStatus === 'loading') { if (this.state.pageStatus === 'loading') {
var pageStatus = 'loaded'; let pageStatus = 'loaded';
callsInfo.forEach((call, index) => { callsInfo.forEach((call, index) => {
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) { if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) {
pageStatus = 'loading'; pageStatus = 'loading';
return;
} }
}) });
if (pageStatus === 'loaded') { if (pageStatus === 'loaded') {
this.setState({ pageStatus: pageStatus }); this.setState({
pageStatus
});
} }
} }
if (this.state.pageStatus === 'loaded'){ if (this.state.pageStatus === 'loaded') {
if (this.state.dateOfRegister === ''){ if (this.state.dateOfRegister === '') {
let transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction){ if (transaction) {
this.setState({ dateOfRegister: transaction.value }); this.setState({
dateOfRegister: transaction.value
});
} }
} }
if (this.state.orbitDBId === ''){ if (this.state.orbitDBId === '') {
let transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction){ if (transaction) {
this.setState({ orbitDBId: transaction.value }); this.setState({
orbitDBId: transaction.value
});
} }
} }
} }
@ -72,11 +81,14 @@ class ProfileInformation extends Component {
render() { render() {
return ( return (
<div className="user-info"> <div className="user-info">
{this.props.avatarUrl && <UserAvatar {this.props.avatarUrl && (
<UserAvatar
size="40" size="40"
className="inline user-avatar" className="inline user-avatar"
src={this.props.avatarUrl} src={this.props.avatarUrl}
name={this.props.username}/>} name={this.props.username}
/>
)}
<table className="highlight centered responsive-table"> <table className="highlight centered responsive-table">
<tbody> <tbody>
<tr> <tr>
@ -99,15 +111,17 @@ class ProfileInformation extends Component {
<td><strong>Number of posts:</strong></td> <td><strong>Number of posts:</strong></td>
<td>{this.props.numberOfPosts}</td> <td>{this.props.numberOfPosts}</td>
</tr> </tr>
{this.state.dateOfRegister && {this.state.dateOfRegister
&& (
<tr> <tr>
<td><strong>Member since:</strong></td> <td><strong>Member since:</strong></td>
<td>{epochTimeConverter(this.state.dateOfRegister)}</td> <td>{epochTimeConverter(this.state.dateOfRegister)}</td>
</tr> </tr>
)
} }
</tbody> </tbody>
</table> </table>
{this.props.self && <UsernameFormContainer/>} {this.props.self && <UsernameFormContainer />}
</div> </div>
); );
} }
@ -116,17 +130,15 @@ class ProfileInformation extends Component {
this.getBlockchainData(); this.getBlockchainData();
} }
componentDidUpdate(){ componentDidUpdate() {
this.getBlockchainData(); this.getBlockchainData();
} }
}; }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
drizzleStatus: state.drizzleStatus, drizzleStatus: state.drizzleStatus,
contracts: state.contracts, contracts: state.contracts,
user: state.user user: state.user
} });
};
export default connect(mapStateToProps)(ProfileInformation); export default connect(mapStateToProps)(ProfileInformation);

121
app/src/components/Topic.js

@ -1,15 +1,15 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router' import { withRouter } from 'react-router-dom';
import ContentLoader from "react-content-loader" import ContentLoader from 'react-content-loader';
import { Card } from 'semantic-ui-react' import { Card } from 'semantic-ui-react';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter' import epochTimeConverter from '../helpers/EpochTimeConverter';
class Topic extends Component { class Topic extends Component {
constructor(props){ constructor(props) {
super(props); super(props);
this.fetchSubject = this.fetchSubject.bind(this); this.fetchSubject = this.fetchSubject.bind(this);
@ -17,70 +17,93 @@ class Topic extends Component {
this.state = { this.state = {
topicSubject: null, topicSubject: null,
topicSubjectFetchStatus: 'pending' topicSubjectFetchStatus: 'pending'
} };
} }
async fetchSubject(topicID) { async fetchSubject(topicID) {
var topicSubject; let topicSubject;
if (this.props.topicData.value[1] === this.props.user.address) { if (this.props.topicData.value[1] === this.props.user.address) {
let orbitData = this.props.orbitDB.topicsDB.get(topicID); const orbitData = this.props.orbitDB.topicsDB.get(topicID);
topicSubject = orbitData['subject'] topicSubject = orbitData.subject;
} else { } else {
const fullAddress = "/orbitdb/" + this.props.topicData.value[0] + "/topics"; const fullAddress = `/orbitdb/${this.props.topicData.value[0]
}/topics`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
let localOrbitData = store.get(topicID); const localOrbitData = store.get(topicID);
if (localOrbitData) { if (localOrbitData) {
topicSubject = localOrbitData['subject']; topicSubject = localOrbitData.subject;
} 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', () => {
topicSubject = store.get(topicID)['subject']; topicSubject = store.get(topicID).subject;
}) });
} }
} }
this.setState({ this.setState({
topicSubject: topicSubject, topicSubject,
topicSubjectFetchStatus: 'fetched' topicSubjectFetchStatus: 'fetched'
}) });
} }
render(){ render() {
return ( return (
<Card link className="card" <Card
onClick={() => {this.props.history.push("/topic/" + this.props.topicID)}}> link
className="card"
onClick={() => {
this.props.history.push(`/topic/${this.props.topicID}`);
}}
>
<Card.Content> <Card.Content>
<div className={"topic-subject" + (this.state.topicSubject ? "" : " grey-text")}> <div className={`topic-subject${
<p><strong> this.state.topicSubject ? '' : ' grey-text'}`}
>
<p>
<strong>
{this.state.topicSubject !== null ? this.state.topicSubject {this.state.topicSubject !== null ? this.state.topicSubject
:<ContentLoader height={5.8} width={300} speed={2} : (
primaryColor="#b2e8e6" secondaryColor="#00b5ad" > <ContentLoader
height={5.8}
width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="150" height="5.5" /> <rect x="0" y="0" rx="3" ry="3" width="150" height="5.5" />
</ContentLoader>} </ContentLoader>
</strong></p> )}
</strong>
</p>
</div> </div>
<hr/> <hr />
<div className="topic-meta"> <div className="topic-meta">
<p className={"no-margin" + <p className={`no-margin${
(this.props.topicData !== null ? "" : " grey-text")}> this.props.topicData !== null ? '' : ' grey-text'}`}
>
{this.props.topicData !== null {this.props.topicData !== null
?this.props.topicData.value[2] ? this.props.topicData.value[2]
:"Username" : 'Username'
} }
</p> </p>
<p className={"no-margin" + <p className={`no-margin${
(this.props.topicData !== null ? "" : " grey-text")}> this.props.topicData !== null ? '' : ' grey-text'}`}
{"Number of replies: " + (this.props.topicData !== null >
?this.props.topicData.value[4].length {`Number of replies: ${this.props.topicData !== null
:"") ? this.props.topicData.value[4].length
: ''}`
} }
</p> </p>
<p className="topic-date grey-text"> <p className="topic-date grey-text">
{this.props.topicData !== null && {this.props.topicData !== null
<TimeAgo date={epochTimeConverter(this.props.topicData.value[3])}/> && (
<TimeAgo
date={epochTimeConverter(this.props.topicData.value[3])}
/>
)
} }
</p> </p>
</div> </div>
@ -90,29 +113,27 @@ class Topic extends Component {
} }
componentDidMount() { componentDidMount() {
if (this.props.topicData !== null && if (this.props.topicData !== null
this.state.topicSubjectFetchStatus === "pending" && && this.state.topicSubjectFetchStatus === 'pending'
this.props.orbitDB.ipfsInitialized && && this.props.orbitDB.ipfsInitialized
this.props.orbitDB.orbitdb) { && this.props.orbitDB.orbitdb) {
this.fetchSubject(this.props.topicID); this.fetchSubject(this.props.topicID);
} }
} }
componentDidUpdate() { componentDidUpdate() {
if (this.props.topicData !== null && if (this.props.topicData !== null
this.state.topicSubjectFetchStatus === "pending" && && this.state.topicSubjectFetchStatus === 'pending'
this.props.orbitDB.ipfsInitialized && && this.props.orbitDB.ipfsInitialized
this.props.orbitDB.orbitdb) { && this.props.orbitDB.orbitdb) {
this.fetchSubject(this.props.topicID); this.fetchSubject(this.props.topicID);
} }
} }
}; }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
user: state.user, user: state.user,
orbitDB: state.orbit orbitDB: state.orbit
} });
}
export default withRouter(connect(mapStateToProps)(Topic)); export default withRouter(connect(mapStateToProps)(Topic));

44
app/src/components/TopicList.js

@ -4,8 +4,8 @@ import { drizzle } from '../index';
import Topic from './Topic'; import Topic from './Topic';
const contract = "Forum"; const contract = 'Forum';
const getTopicMethod = "getTopic"; const getTopicMethod = 'getTopic';
class TopicList extends Component { class TopicList extends Component {
constructor(props) { constructor(props) {
@ -15,22 +15,24 @@ class TopicList extends Component {
this.state = { this.state = {
dataKeys: [] dataKeys: []
} };
} }
getBlockchainData(){ getBlockchainData() {
if (this.props.drizzleStatus['initialized']){ if (this.props.drizzleStatus.initialized) {
let dataKeysShallowCopy = this.state.dataKeys.slice(); const dataKeysShallowCopy = this.state.dataKeys.slice();
let fetchingNewData = false; let fetchingNewData = false;
this.props.topicIDs.forEach( topicID => { this.props.topicIDs.forEach((topicID) => {
if (!this.state.dataKeys[topicID]) { if (!this.state.dataKeys[topicID]) {
dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(topicID); dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(
topicID,
);
fetchingNewData = true; fetchingNewData = true;
} }
}) });
if (fetchingNewData){ if (fetchingNewData) {
this.setState({ this.setState({
dataKeys: dataKeysShallowCopy dataKeys: dataKeysShallowCopy
}); });
@ -39,14 +41,16 @@ class TopicList extends Component {
} }
render() { render() {
const topics = this.props.topicIDs.map( topicID => { const topics = this.props.topicIDs.map(topicID => (
return (<Topic <Topic
topicData={(this.state.dataKeys[topicID] && this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]]) topicData={(this.state.dataKeys[topicID]
&& this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]])
? this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]] ? this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]]
: null} : null}
topicID={topicID} topicID={topicID}
key={topicID} />) key={topicID}
}); />
));
return ( return (
<div className="topics-list"> <div className="topics-list">
@ -59,16 +63,14 @@ class TopicList extends Component {
this.getBlockchainData(); this.getBlockchainData();
} }
componentDidUpdate(){ componentDidUpdate() {
this.getBlockchainData(); this.getBlockchainData();
} }
}; }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
contracts: state.contracts, contracts: state.contracts,
drizzleStatus: state.drizzleStatus drizzleStatus: state.drizzleStatus
} });
};
export default connect(mapStateToProps)(TopicList); export default connect(mapStateToProps)(TopicList);

4
app/src/config/drizzleOptions.js

@ -1,4 +1,4 @@
import Forum from "../contracts/Forum.json"; import Forum from '../contracts/Forum.json';
const drizzleOptions = { const drizzleOptions = {
web3: { web3: {
@ -14,7 +14,7 @@ const drizzleOptions = {
polls: { polls: {
accounts: 2000, accounts: 2000,
blocks: 2000 blocks: 2000
}, }
}; };
export default drizzleOptions; export default drizzleOptions;

4
app/src/config/ipfsOptions.js

@ -3,11 +3,13 @@
const ipfsOptions = { const ipfsOptions = {
EXPERIMENTAL: { EXPERIMENTAL: {
pubsub: true pubsub: true
}, config: { },
config: {
Addresses: { Addresses: {
Swarm: [ Swarm: [
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star', '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star',
// Use local signal server (https://github.com/libp2p/js-libp2p-websocket-star-rendezvous) // Use local signal server (https://github.com/libp2p/js-libp2p-websocket-star-rendezvous)
// (e.g. rendezvous --port=9090 --host=127.0.0.1)
'/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star' '/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star'
] ]
} }

87
app/src/containers/BoardContainer.js

@ -1,74 +1,81 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { drizzle } from '../index'; import { withRouter } from 'react-router-dom';
import { withRouter } from 'react-router-dom'
import { Header } from 'semantic-ui-react'; import { Header } from 'semantic-ui-react';
import { drizzle } from '../index';
import TopicList from '../components/TopicList'; import TopicList from '../components/TopicList';
import FloatingButton from '../components/FloatingButton'; import FloatingButton from '../components/FloatingButton';
/*import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions';*/ /* import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions'; */
const contract = "Forum"; const contract = 'Forum';
const getNumberOfTopicsMethod = "getNumberOfTopics"; const getNumberOfTopicsMethod = 'getNumberOfTopics';
class BoardContainer extends Component { class BoardContainer extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
/*this.props.store.dispatch(showProgressBar());*/ /* this.props.store.dispatch(showProgressBar()); */
this.getBlockchainData = this.getBlockchainData.bind(this); this.getBlockchainData = this.getBlockchainData.bind(this);
this.handleCreateTopicClick = this.handleCreateTopicClick.bind(this); this.handleCreateTopicClick = this.handleCreateTopicClick.bind(this);
this.state = { this.state = {
pageStatus: 'initialized' pageStatus: 'initialized'
} };
} }
getBlockchainData() { getBlockchainData() {
if (this.state.pageStatus === 'initialized' && if (this.state.pageStatus === 'initialized'
this.props.drizzleStatus['initialized']){ && this.props.drizzleStatus.initialized) {
this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall(); this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall();
this.setState({ pageStatus: 'loading' }); this.setState({
pageStatus: 'loading'
});
} }
if (this.state.pageStatus === 'loading' && if (this.state.pageStatus === 'loading'
this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]){ && this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]) {
this.setState({ pageStatus: 'loaded' }); this.setState({
/*this.props.store.dispatch(hideProgressBar());*/ pageStatus: 'loaded'
});
/* this.props.store.dispatch(hideProgressBar()); */
} }
} }
handleCreateTopicClick() { handleCreateTopicClick() {
this.props.history.push("/startTopic"); this.props.history.push('/startTopic');
} }
render() { render() {
var boardContents; let boardContents;
if (this.state.pageStatus === 'loaded'){ if (this.state.pageStatus === 'loaded') {
var numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; const numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value;
if (numberOfTopics !== '0'){ if (numberOfTopics !== '0') {
this.topicIDs = []; this.topicIDs = [];
for (var i = 0; i < numberOfTopics; i++) { for (let i = 0; i < numberOfTopics; i++) {
this.topicIDs.push(i); this.topicIDs.push(i);
} }
boardContents = ([ boardContents = ([
<TopicList topicIDs={this.topicIDs} key="topicList"/>, <TopicList topicIDs={this.topicIDs} key="topicList" />,
<div className="bottom-overlay-pad" key="pad"></div>, <div className="bottom-overlay-pad" key="pad" />,
this.props.hasSignedUp && this.props.hasSignedUp
<FloatingButton onClick={this.handleCreateTopicClick} && (
key="createTopicButton"/> <FloatingButton
onClick={this.handleCreateTopicClick}
key="createTopicButton"
/>
)
]); ]);
} else { } else if (!this.props.hasSignedUp) {
if (!this.props.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">
There are no topics yet! There are no topics yet!
</Header> </Header>
<Header color='teal' textAlign='center' as='h4'> <Header color="teal" textAlign="center" as="h4">
Sign up to be the first to post. Sign up to be the first to post.
</Header> </Header>
</div> </div>
@ -76,19 +83,21 @@ class BoardContainer extends Component {
} else { } else {
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">
There are no topics yet! There are no topics yet!
</Header> </Header>
<Header color='teal' textAlign='center' as='h4'> <Header color="teal" textAlign="center" as="h4">
Click the add button at the bottom of the page to be the first to post. Click the add button at the bottom of the page to be the first
to post.
</Header> </Header>
<FloatingButton onClick={this.handleCreateTopicClick} <FloatingButton
key="createTopicButton"/> onClick={this.handleCreateTopicClick}
key="createTopicButton"
/>
</div> </div>
); );
} }
} }
}
return ( return (
<div className="fill"> <div className="fill">
@ -101,17 +110,15 @@ class BoardContainer extends Component {
this.getBlockchainData(); this.getBlockchainData();
} }
componentDidUpdate(){ componentDidUpdate() {
this.getBlockchainData(); this.getBlockchainData();
} }
} }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
contracts: state.contracts, contracts: state.contracts,
drizzleStatus: state.drizzleStatus, drizzleStatus: state.drizzleStatus,
hasSignedUp: state.user.hasSignedUp hasSignedUp: state.user.hasSignedUp
} });
};
export default withRouter(connect(mapStateToProps)(BoardContainer)); export default withRouter(connect(mapStateToProps)(BoardContainer));

17
app/src/containers/CoreLayoutContainer.js

@ -2,10 +2,8 @@ import React, { Component } from 'react';
import NavBarContainer from './NavBarContainer'; import NavBarContainer from './NavBarContainer';
import RightSideBarContainer from './TransactionsMonitorContainer'; import RightSideBarContainer from './TransactionsMonitorContainer';
/*import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer';*/
// Styles // Styles
import '../assets/fonts/fontawesome-free-5.7.2/all.js'; //TODO: check https://fontawesome.com/how-to-use/on-the-web/setup/using-package-managers import '../assets/fonts/fontawesome-free-5.7.2/all.js'; // TODO: check https://fontawesome.com/how-to-use/on-the-web/setup/using-package-managers
import '../assets/css/App.css'; import '../assets/css/App.css';
import '../assets/css/sign-up-container.css'; import '../assets/css/sign-up-container.css';
@ -14,20 +12,21 @@ import '../assets/css/start-topic-container.css';
import '../assets/css/topic-container.css'; import '../assets/css/topic-container.css';
import '../assets/css/profile-container.css'; import '../assets/css/profile-container.css';
/* import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer'; */
class CoreLayout extends Component { class CoreLayout extends Component {
render() { render() {
return ( return (
<div className="App"> <div className="App">
<NavBarContainer/> <NavBarContainer />
{/*<div className="progress-bar-container" {/* <div className="progress-bar-container"
style={{display: this.props.isProgressBarVisible ? "block" : "none"}}> style={{display: this.props.isProgressBarVisible ? "block" : "none"}}>
<div className="progress"> <div className="progress">
<div className="indeterminate"></div> <div className="indeterminate"></div>
</div> </div>
</div>*/} </div> */}
<div className="page-container"> <div className="page-container">
<aside className="left-side-panel"> <aside className="left-side-panel" />
</aside>
<div className="main-panel"> <div className="main-panel">
<div className="view-container"> <div className="view-container">
{this.props.children} {this.props.children}
@ -42,4 +41,4 @@ class CoreLayout extends Component {
} }
} }
export default CoreLayout export default CoreLayout;

8
app/src/containers/HomeContainer.js

@ -4,9 +4,9 @@ import BoardContainer from './BoardContainer';
class HomeContainer extends Component { class HomeContainer extends Component {
render() { render() {
//We can add a modal to tell the user to sign up // We can add a modal to tell the user to sign up
/*var modal = this.props.user.hasSignedUp && ( /* var modal = this.props.user.hasSignedUp && (
<Modal dimmer='blurring' open={this.state.open}> <Modal dimmer='blurring' open={this.state.open}>
<Modal.Header>Select a Photo</Modal.Header> <Modal.Header>Select a Photo</Modal.Header>
<Modal.Content image> <Modal.Content image>
@ -17,9 +17,9 @@ class HomeContainer extends Component {
<p>Is it okay to use this photo?</p> <p>Is it okay to use this photo?</p>
</Modal.Description> </Modal.Description>
</Modal.Content> </Modal.Content>
</Modal>);*/ </Modal>); */
return (<BoardContainer/>); return (<BoardContainer />);
} }
} }

42
app/src/containers/NavBarContainer.js

@ -1,38 +1,50 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
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';
import { Image, Menu } from 'semantic-ui-react' 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 { class NavBarContainer extends Component {
render() { render() {
return ( return (
<Menu fixed='top' inverted> <Menu fixed="top" inverted>
<Menu.Item header onClick={() => {this.props.navigateTo('/')}}> <Menu.Item header onClick={() => { this.props.navigateTo('/'); }}>
<Image <Image
size='mini' size="mini"
src={logo} src={logo}
style={{ marginRight: '1.5em' }} style={{
marginRight: '1.5em'
}}
/> />
Apella Apella
</Menu.Item> </Menu.Item>
<Menu.Item onClick={() => {this.props.navigateTo('/home')}}> <Menu.Item onClick={() => { this.props.navigateTo('/home'); }}>
Home Home
</Menu.Item> </Menu.Item>
{this.props.hasSignedUp {this.props.hasSignedUp
?<Menu.Item onClick={() => {this.props.navigateTo('/profile')}}> ? (
<Menu.Item onClick={() => { this.props.navigateTo('/profile'); }}>
Profile Profile
</Menu.Item> </Menu.Item>
:<Menu.Menu position='right' style={{backgroundColor: '#00b5ad'}}> )
<Menu.Item onClick={() => {this.props.navigateTo('/signup')}}> : (
<Menu.Menu
position="right"
style={{
backgroundColor: '#00b5ad'
}}
>
<Menu.Item onClick={() => { this.props.navigateTo('/signup'); }}>
SignUp SignUp
</Menu.Item> </Menu.Item>
</Menu.Menu> </Menu.Menu>
)
} }
<div className="navBarText"> <div className="navBarText">
{this.props.navBarTitle !== '' && <span>{this.props.navBarTitle}</span>} {this.props.navBarTitle !== ''
&& <span>{this.props.navBarTitle}</span>}
</div> </div>
</Menu> </Menu>
); );
@ -40,14 +52,12 @@ class NavBarContainer extends Component {
} }
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location) navigateTo: location => push(location)
}, dispatch); }, dispatch);
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
hasSignedUp: state.user.hasSignedUp, hasSignedUp: state.user.hasSignedUp,
navBarTitle: state.interface.navBarTitle navBarTitle: state.interface.navBarTitle
} });
};
export default connect(mapStateToProps, mapDispatchToProps)(NavBarContainer); export default connect(mapStateToProps, mapDispatchToProps)(NavBarContainer);

133
app/src/containers/ProfileContainer.js

@ -1,10 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
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 { drizzle } from '../index'; import { drizzle } from '../index';
import { Tab } from 'semantic-ui-react'
import ProfileInformation from '../components/ProfileInformation'; import ProfileInformation from '../components/ProfileInformation';
import TopicList from '../components/TopicList'; import TopicList from '../components/TopicList';
@ -12,13 +12,14 @@ import PostList from '../components/PostList';
import LoadingSpinner from '../components/LoadingSpinner'; import LoadingSpinner from '../components/LoadingSpinner';
import { setNavBarTitle } from '../redux/actions/userInterfaceActions'; import { setNavBarTitle } from '../redux/actions/userInterfaceActions';
const callsInfo = [{ const callsInfo = [
{
contract: 'Forum', contract: 'Forum',
method: 'getUsername' method: 'getUsername'
},{ }, {
contract: 'Forum', contract: 'Forum',
method: 'getUserTopics' method: 'getUserTopics'
},{ }, {
contract: 'Forum', contract: 'Forum',
method: 'getUserPosts' method: 'getUserPosts'
} }
@ -31,7 +32,7 @@ class ProfileContainer extends Component {
this.getBlockchainData = this.getBlockchainData.bind(this); this.getBlockchainData = this.getBlockchainData.bind(this);
this.dataKey = []; this.dataKey = [];
var address = this.props.match.params.address const address = this.props.match.params.address
? this.props.match.params.address ? this.props.match.params.address
: this.props.user.address; : this.props.user.address;
@ -45,115 +46,131 @@ class ProfileContainer extends Component {
} }
getBlockchainData() { getBlockchainData() {
if (this.state.pageStatus === 'initialized' && if (this.state.pageStatus === 'initialized'
this.props.drizzleStatus['initialized']) { && this.props.drizzleStatus.initialized) {
callsInfo.forEach((call, index) => { callsInfo.forEach((call, index) => {
this.dataKey[index] = drizzle.contracts[call.contract] this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall(
.methods[call.method].cacheCall(this.state.userAddress); this.state.userAddress,
}) );
this.setState({ pageStatus: 'loading' }); });
this.setState({
pageStatus: 'loading'
});
} }
if (this.state.pageStatus === 'loading') { if (this.state.pageStatus === 'loading') {
var pageStatus = 'loaded'; let pageStatus = 'loaded';
callsInfo.forEach((call, index) => { callsInfo.forEach((call, index) => {
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) { if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) {
pageStatus = 'loading'; pageStatus = 'loading';
return;
} }
}) });
if (pageStatus === 'loaded') { if (pageStatus === 'loaded') {
this.setState({ pageStatus: pageStatus }); this.setState({
pageStatus
});
} }
} }
if (this.state.pageStatus === 'loaded'){ if (this.state.pageStatus === 'loaded') {
if (this.state.username === ''){ if (this.state.username === '') {
let transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction){ if (transaction) {
var username = transaction.value; const username = transaction.value;
this.props.setNavBarTitle(username); this.props.setNavBarTitle(username);
this.setState({ username: username }); this.setState({
username
});
} }
} }
if (this.state.topicIDs === null){ if (this.state.topicIDs === null) {
let transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction){ if (transaction) {
this.setState({ topicIDs: transaction.value }); this.setState({
topicIDs: transaction.value
});
} }
} }
if (this.state.postIDs === null){ if (this.state.postIDs === null) {
let transaction = this.props.contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]]; const transaction = this.props.contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]];
if (transaction){ if (transaction) {
this.setState({ postIDs: transaction.value }); this.setState({
postIDs: transaction.value
});
} }
} }
/*this.props.store.dispatch(hideProgressBar());*/ /* this.props.store.dispatch(hideProgressBar()); */
} }
} }
render() { render() {
if (!this.props.user.hasSignedUp) { if (!this.props.user.hasSignedUp) {
this.props.navigateTo("/signup"); this.props.navigateTo('/signup');
return(null); return (null);
} }
var infoTab = const infoTab = (
(<ProfileInformation <ProfileInformation
address={this.state.userAddress} address={this.state.userAddress}
username={this.state.username} username={this.state.username}
numberOfTopics={this.state.topicIDs && this.state.topicIDs.length} numberOfTopics={this.state.topicIDs && this.state.topicIDs.length}
numberOfPosts={this.state.postIDs && this.state.postIDs.length} numberOfPosts={this.state.postIDs && this.state.postIDs.length}
self={this.state.userAddress === this.props.user.address} self={this.state.userAddress === this.props.user.address}
key="profileInfo" key="profileInfo"
/>); />
var topicsTab = );
(<div className="profile-tab"> const topicsTab = (
<div className="profile-tab">
{this.state.topicIDs {this.state.topicIDs
? <TopicList topicIDs={this.state.topicIDs} /> ? <TopicList topicIDs={this.state.topicIDs} />
: <LoadingSpinner /> : <LoadingSpinner />
} }
</div>); </div>
var postsTab = );
(<div className="profile-tab"> const postsTab = (
<div className="profile-tab">
{this.state.postIDs {this.state.postIDs
? <PostList postIDs={this.state.postIDs} recentToTheTop /> ? <PostList postIDs={this.state.postIDs} recentToTheTop />
: <LoadingSpinner /> : <LoadingSpinner />
} }
</div>); </div>
);
const profilePanes = [ const profilePanes = [
{ {
menuItem: 'INFORMATION', menuItem: 'INFORMATION',
pane: { pane: {
key: 'INFORMATION', key: 'INFORMATION',
content: (infoTab), content: (infoTab)
}, }
}, },
{ {
menuItem: 'TOPICS', menuItem: 'TOPICS',
pane: { pane: {
key: 'TOPICS', key: 'TOPICS',
content: (topicsTab), content: (topicsTab)
}, }
}, },
{ {
menuItem: 'POSTS', menuItem: 'POSTS',
pane: { pane: {
key: 'POSTS', key: 'POSTS',
content: (postsTab), content: (postsTab)
}, }
}, }
] ];
return ( return (
<div> <div>
<Tab <Tab
menu={{ secondary: true, pointing: true }} menu={{
secondary: true, pointing: true
}}
panes={profilePanes} panes={profilePanes}
renderActiveOnly={false} /> renderActiveOnly={false}
/>
</div> </div>
); );
} }
@ -162,7 +179,7 @@ class ProfileContainer extends Component {
this.getBlockchainData(); this.getBlockchainData();
} }
componentDidUpdate(){ componentDidUpdate() {
this.getBlockchainData(); this.getBlockchainData();
} }
@ -172,17 +189,15 @@ class ProfileContainer extends Component {
} }
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location), navigateTo: location => push(location),
setNavBarTitle: (navBarTitle) => setNavBarTitle(navBarTitle) setNavBarTitle: navBarTitle => setNavBarTitle(navBarTitle)
}, dispatch); }, dispatch);
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
user: state.user, user: state.user,
drizzleStatus: state.drizzleStatus, drizzleStatus: state.drizzleStatus,
contracts: state.contracts, contracts: state.contracts,
orbitDB: state.orbitDB orbitDB: state.orbitDB
} });
};
export default connect(mapStateToProps, mapDispatchToProps)(ProfileContainer); export default connect(mapStateToProps, mapDispatchToProps)(ProfileContainer);

31
app/src/containers/SignUpContainer.js

@ -1,43 +1,46 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Header } from 'semantic-ui-react'; import { Header } from 'semantic-ui-react';
import {connect} from "react-redux"; import { connect } from 'react-redux';
import UsernameFormContainer from './UsernameFormContainer'; import UsernameFormContainer from './UsernameFormContainer';
class SignUpContainer extends Component { class SignUpContainer extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.user.hasSignedUp && !prevProps.user.hasSignedUp) if (this.props.user.hasSignedUp && !prevProps.user.hasSignedUp) this.props.history.push('/');
this.props.history.push("/");
} }
render() { render() {
return ( return (
this.props.user.hasSignedUp this.props.user.hasSignedUp
?(<div className="vertical-center-in-parent"> ? (
<Header color='teal' textAlign='center' as='h2'> <div className="vertical-center-in-parent">
<Header color="teal" textAlign="center" as="h2">
There is already an account for this addresss. There is already an account for this addresss.
</Header> </Header>
<Header color='teal' textAlign='center' as='h4'> <Header color="teal" textAlign="center" as="h4">
If you want to create another account please change your address. If you want to create another account please change your address.
</Header> </Header>
</div>) </div>
:(<div className="sign-up-container"> )
: (
<div className="sign-up-container">
<div> <div>
<h1>Sign Up</h1> <h1>Sign Up</h1>
<p className="no-margin"> <p className="no-margin">
<strong>Account address:</strong> {this.props.user.address} <strong>Account address:</strong>
{' '}
{this.props.user.address}
</p> </p>
<UsernameFormContainer /> <UsernameFormContainer />
</div> </div>
</div>) </div>
)
); );
} }
} }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
user: state.user user: state.user
} });
};
export default connect(mapStateToProps)(SignUpContainer); export default connect(mapStateToProps)(SignUpContainer);

91
app/src/containers/StartTopicContainer.js

@ -1,8 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Form, TextArea, Button, Icon } from 'semantic-ui-react' import { Button, Form, Icon, TextArea } from 'semantic-ui-react';
import NewTopicPreview from '../components/NewTopicPreview' import NewTopicPreview from '../components/NewTopicPreview';
import { createTopic } from '../redux/actions/transactionsActions'; import { createTopic } from '../redux/actions/transactionsActions';
@ -20,12 +20,13 @@ class StartTopicContainer extends Component {
topicSubjectInputEmptySubmit: false, topicSubjectInputEmptySubmit: false,
topicMessageInputEmptySubmit: false, topicMessageInputEmptySubmit: false,
previewEnabled: false, previewEnabled: false,
previewDate: "" previewDate: ''
}; };
} }
async validateAndPost() { async validateAndPost() {
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput === ''){ if (this.state.topicSubjectInput === '' || this.state.topicMessageInput
=== '') {
this.setState({ this.setState({
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '',
topicMessageInputEmptySubmit: this.state.topicMessageInput === '' topicMessageInputEmptySubmit: this.state.topicMessageInput === ''
@ -38,14 +39,16 @@ class StartTopicContainer extends Component {
{ {
topicSubject: this.state.topicSubjectInput, topicSubject: this.state.topicSubjectInput,
topicMessage: this.state.topicMessageInput topicMessage: this.state.topicMessageInput
} },
) ),
); );
this.props.history.push("/home"); this.props.history.push('/home');
} }
handleInputChange(event) { handleInputChange(event) {
this.setState({[event.target.name]: event.target.value}); this.setState({
[event.target.name]: event.target.value
});
} }
handlePreviewToggle() { handlePreviewToggle() {
@ -57,62 +60,80 @@ class StartTopicContainer extends Component {
getDate() { getDate() {
const currentdate = new Date(); const currentdate = new Date();
return ((currentdate.getMonth() + 1) + " " return (`${currentdate.getMonth() + 1} ${
+ currentdate.getDate() + ", " currentdate.getDate()}, ${
+ currentdate.getFullYear() + ", " currentdate.getFullYear()}, ${
+ currentdate.getHours() + ":" currentdate.getHours()}:${
+ currentdate.getMinutes() + ":" currentdate.getMinutes()}:${
+ currentdate.getSeconds()); currentdate.getSeconds()}`);
} }
render() { render() {
if (!this.props.user.hasSignedUp) { if (!this.props.user.hasSignedUp) {
this.props.history.push("/signup"); this.props.history.push('/signup');
return(null); return (null);
} }
var previewEditText = this.state.previewEnabled ? "Edit" : "Preview"; const previewEditText = this.state.previewEnabled ? 'Edit' : 'Preview';
return ( return (
<div> <div>
{this.state.previewEnabled && {this.state.previewEnabled
&& (
<NewTopicPreview <NewTopicPreview
date={this.state.previewDate} date={this.state.previewDate}
subject={this.state.topicSubjectInput} subject={this.state.topicSubjectInput}
content={this.state.topicMessageInput} content={this.state.topicMessageInput}
/> />
)
} }
<Form> <Form>
{!this.state.previewEnabled && {!this.state.previewEnabled
[<Form.Field key={"topicSubjectInput"}> && [
<Form.Input name={"topicSubjectInput"} <Form.Field key="topicSubjectInput">
<Form.Input
name="topicSubjectInput"
error={this.state.topicSubjectInputEmptySubmit} error={this.state.topicSubjectInputEmptySubmit}
type="text" type="text"
value={this.state.topicSubjectInput} value={this.state.topicSubjectInput}
placeholder="Subject" placeholder="Subject"
id="topicSubjectInput" id="topicSubjectInput"
onChange={this.handleInputChange} /> onChange={this.handleInputChange}
/>
</Form.Field>, </Form.Field>,
<TextArea key={"topicMessageInput"} <TextArea
name={"topicMessageInput"} key="topicMessageInput"
className={this.state.topicMessageInputEmptySubmit ? "form-textarea-required" : ""} name="topicMessageInput"
className={this.state.topicMessageInputEmptySubmit
? 'form-textarea-required'
: ''}
value={this.state.topicMessageInput} value={this.state.topicMessageInput}
placeholder="Post" placeholder="Post"
id="topicMessageInput" id="topicMessageInput"
rows={5} rows={5}
autoHeight autoHeight
onChange={this.handleInputChange} />] onChange={this.handleInputChange}
/>]
} }
<br/><br/> <br />
<br />
<Button.Group> <Button.Group>
<Button animated key="submit" type="button" color='teal' <Button
onClick={this.validateAndPost}> animated
key="submit"
type="button"
color="teal"
onClick={this.validateAndPost}
>
<Button.Content visible>Post</Button.Content> <Button.Content visible>Post</Button.Content>
<Button.Content hidden> <Button.Content hidden>
<Icon name='send' /> <Icon name="send" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button type="button" color='yellow' <Button
onClick={this.handlePreviewToggle}> type="button"
color="yellow"
onClick={this.handlePreviewToggle}
>
{previewEditText} {previewEditText}
</Button> </Button>
</Button.Group> </Button.Group>
@ -122,11 +143,9 @@ class StartTopicContainer extends Component {
} }
} }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
orbitDB: state.orbitDB, orbitDB: state.orbitDB,
user: state.user user: state.user
} });
};
export default connect(mapStateToProps)(StartTopicContainer); export default connect(mapStateToProps)(StartTopicContainer);

121
app/src/containers/TopicContainer.js

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
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 { drizzle } from '../index'; import { drizzle } from '../index';
@ -10,15 +10,15 @@ import FloatingButton from '../components/FloatingButton';
import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js'; import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js';
const contract = "Forum"; const contract = 'Forum';
const getTopicMethod = "getTopic"; const getTopicMethod = 'getTopic';
class TopicContainer extends Component { class TopicContainer extends Component {
constructor(props) { constructor(props) {
super(props); super(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(this.props.match.params.topicId)) {
this.props.navigateTo('/404'); this.props.navigateTo('/404');
} }
@ -31,7 +31,8 @@ class TopicContainer extends Component {
pageStatus: 'initialized', pageStatus: 'initialized',
topicID: parseInt(this.props.match.params.topicId), topicID: parseInt(this.props.match.params.topicId),
topicSubject: null, topicSubject: null,
postFocus: this.props.match.params.postId && /^[0-9]+$/.test(this.props.match.params.postId) postFocus: this.props.match.params.postId
&& /^[0-9]+$/.test(this.props.match.params.postId)
? this.props.match.params.postId ? this.props.match.params.postId
: null, : null,
fetchTopicSubjectStatus: 'pending', fetchTopicSubjectStatus: 'pending',
@ -40,56 +41,71 @@ class TopicContainer extends Component {
} }
getBlockchainData() { getBlockchainData() {
if (this.state.pageStatus === 'initialized' && if (this.state.pageStatus === 'initialized'
this.props.drizzleStatus['initialized']) { && this.props.drizzleStatus.initialized) {
this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(this.state.topicID); this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(
this.setState({ pageStatus: 'loading' }); this.state.topicID,
);
this.setState({
pageStatus: 'loading'
});
} }
if (this.state.pageStatus === 'loading' && if (this.state.pageStatus === 'loading'
this.props.contracts[contract][getTopicMethod][this.dataKey]) { && this.props.contracts[contract][getTopicMethod][this.dataKey]) {
this.setState({ pageStatus: 'loaded' }); this.setState({
if (this.props.orbitDB.orbitdb !== null){ pageStatus: 'loaded'
this.fetchTopicSubject(this.props.contracts[contract][getTopicMethod][this.dataKey].value[0]); });
this.setState({ fetchTopicSubjectStatus: 'fetching' }); if (this.props.orbitDB.orbitdb !== null) {
this.fetchTopicSubject(
this.props.contracts[contract][getTopicMethod][this.dataKey].value[0],
);
this.setState({
fetchTopicSubjectStatus: 'fetching'
});
} }
} }
if (this.state.pageStatus === 'loaded' && if (this.state.pageStatus === 'loaded'
this.state.fetchTopicSubjectStatus === 'pending' && && this.state.fetchTopicSubjectStatus === 'pending'
this.props.orbitDB.orbitdb !== null) { && this.props.orbitDB.orbitdb !== null) {
this.fetchTopicSubject(this.props.contracts[contract][getTopicMethod][this.dataKey].value[0]); this.fetchTopicSubject(
this.setState({ fetchTopicSubjectStatus: 'fetching' }); this.props.contracts[contract][getTopicMethod][this.dataKey].value[0],
);
this.setState({
fetchTopicSubjectStatus: 'fetching'
});
} }
} }
async fetchTopicSubject(orbitDBAddress) { async fetchTopicSubject(orbitDBAddress) {
let orbitData; let orbitData;
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1] === this.props.user.address) { if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1]
=== this.props.user.address) {
orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID); orbitData = this.props.orbitDB.topicsDB.get(this.state.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 this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
let localOrbitData = store.get(this.state.topicID); const localOrbitData = store.get(this.state.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(this.state.topicID);
}) });
} }
} }
this.props.setNavBarTitle(orbitData['subject']); this.props.setNavBarTitle(orbitData.subject);
this.setState({ this.setState({
topicSubject: orbitData['subject'], topicSubject: orbitData.subject,
fetchTopicSubjectStatus: 'fetched' fetchTopicSubjectStatus: 'fetched'
}); });
} }
togglePostingState(event) { togglePostingState(event) {
if (event){ if (event) {
event.preventDefault(); event.preventDefault();
} }
this.setState(prevState => ({ this.setState(prevState => ({
@ -97,40 +113,49 @@ class TopicContainer extends Component {
})); }));
} }
postCreated(){ postCreated() {
this.setState(prevState => ({ this.setState(prevState => ({
posting: false posting: false
})); }));
} }
render() { render() {
var topicContents; let topicContents;
if (this.state.pageStatus === 'loaded') { if (this.state.pageStatus === 'loaded') {
topicContents = ( topicContents = (
(<div> (
<PostList postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]} <div>
focusOnPost={this.state.postFocus ? this.state.postFocus : null}/> <PostList
{this.state.posting && postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]}
<NewPost topicID={this.state.topicID} focusOnPost={this.state.postFocus
? this.state.postFocus
: null}
/>
{this.state.posting
&& (
<NewPost
topicID={this.state.topicID}
subject={this.state.topicSubject} subject={this.state.topicSubject}
postIndex={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4].length} postIndex={this.props.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> <div className="posts-list-spacer" />
{this.props.user.hasSignedUp && !this.state.posting && {this.props.user.hasSignedUp && !this.state.posting
<FloatingButton onClick={this.togglePostingState}/> && <FloatingButton onClick={this.togglePostingState} />
} }
</div>) </div>
) )
);
} }
return ( return (
<div className="fill"> <div className="fill">
{topicContents} {topicContents}
{!this.state.posting && {!this.state.posting
<div className="bottom-overlay-pad"></div> && <div className="bottom-overlay-pad" />
} }
</div> </div>
); );
@ -150,17 +175,15 @@ class TopicContainer extends Component {
} }
const mapDispatchToProps = dispatch => bindActionCreators({ const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location), navigateTo: location => push(location),
setNavBarTitle: (navBarTitle) => setNavBarTitle(navBarTitle) setNavBarTitle: navBarTitle => setNavBarTitle(navBarTitle)
}, dispatch); }, dispatch);
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
user: state.user, user: state.user,
contracts: state.contracts, contracts: state.contracts,
drizzleStatus: state.drizzleStatus, drizzleStatus: state.drizzleStatus,
orbitDB: state.orbit orbitDB: state.orbit
} });
};
export default connect(mapStateToProps, mapDispatchToProps)(TopicContainer); export default connect(mapStateToProps, mapDispatchToProps)(TopicContainer);

105
app/src/containers/TransactionsMonitorContainer.js

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
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';
@ -13,38 +13,37 @@ class RightSideBar extends Component {
this.state = { this.state = {
isTransactionMessageDismissed: [] isTransactionMessageDismissed: []
} };
} }
handleMessageClick(index) { handleMessageClick(index) {
let transactionHash = this.props.transactionStack[index]; const transactionHash = this.props.transactionStack[index];
if (this.props.transactions[transactionHash]) { if (this.props.transactions[transactionHash]) {
if (this.props.transactions[transactionHash].status === 'error') { if (this.props.transactions[transactionHash].status === 'error') {
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
} else { } else if (this.props.transactions[transactionHash].receipt
if (this.props.transactions[transactionHash].receipt && && this.props.transactions[transactionHash].receipt.events) {
this.props.transactions[transactionHash].receipt.events) { switch (Object.keys(
switch (Object.keys(this.props.transactions[transactionHash].receipt.events)[0]){ this.props.transactions[transactionHash].receipt.events,
)[0]) {
case 'UserSignedUp': case 'UserSignedUp':
this.props.history.push("/profile"); this.props.history.push('/profile');
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
break; break;
case 'UsernameUpdated': case 'UsernameUpdated':
this.props.history.push("/profile"); this.props.history.push('/profile');
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
break; break;
case 'TopicCreated': case 'TopicCreated':
this.props.history.push("/topic/" + this.props.history.push(`/topic/${
this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID this.props.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/" + this.props.history.push(`/topic/${
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID + this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID
"/" + }/${
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID}`);
);
this.handleMessageDismiss(null, index); this.handleMessageDismiss(null, index);
break; break;
default: default:
@ -54,14 +53,13 @@ class RightSideBar extends Component {
} }
} }
} }
}
handleMessageDismiss(event, messageIndex) { handleMessageDismiss(event, messageIndex) {
if (event !== null) { if (event !== null) {
event.stopPropagation(); event.stopPropagation();
} }
let isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice(); const isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice();
isTransactionMessageDismissedShallowCopy[messageIndex] = true; isTransactionMessageDismissedShallowCopy[messageIndex] = true;
this.setState({ this.setState({
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy
@ -69,62 +67,73 @@ class RightSideBar extends Component {
} }
render() { render() {
if (this.props.transactionStack.length === 0){ if (this.props.transactionStack.length === 0) {
return null; return null;
} }
let transactionMessages = this.props.transactionStack.map((transaction, index) => { const transactionMessages = this.props.transactionStack.map(
if (this.state.isTransactionMessageDismissed[index]){ (transaction, index) => {
if (this.state.isTransactionMessageDismissed[index]) {
return null; return null;
} }
let color = 'black'; let color = 'black';
let message = []; const message = [];
message.push("New transaction has been queued and is waiting your confirmation."); message.push(
'New transaction has been queued and is waiting your confirmation.',
);
if (this.props.transactions[transaction]) { if (this.props.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 (this.props.transactions[transaction]
this.props.transactions[transaction].status === 'success') { && this.props.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 (this.props.transactions[transaction]
this.props.transactions[transaction].status === "error"){ && this.props.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');
color = 'red'; color = 'red';
message.push(<br key="fail"/>); message.push(<br key="fail" />);
message.push("Transaction failed to complete!"); message.push('Transaction failed to complete!');
} }
return ( return (
<div className="sidebar-message" key={index} <div
onClick={() => {this.handleMessageClick(index)}} > className="sidebar-message"
<Message color={color} key={index}
onDismiss={(e) => {this.handleMessageDismiss(e, index)}}> onClick={() => { this.handleMessageClick(index); }}
>
<Message
color={color}
onDismiss={(e) => {
this.handleMessageDismiss(e, index);
}}
>
{message} {message}
</Message> </Message>
</div> </div>
); );
}); },
);
return (transactionMessages); return (transactionMessages);
} }
} }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
transactions: state.transactions, transactions: state.transactions,
transactionStack: state.transactionStack transactionStack: state.transactionStack
} });
};
const RightSideBarContainer = withRouter(connect(mapStateToProps)(RightSideBar)); const RightSideBarContainer = withRouter(
connect(mapStateToProps)(RightSideBar),
);
export default RightSideBarContainer; export default RightSideBarContainer;

123
app/src/containers/UsernameFormContainer.js

@ -1,15 +1,15 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { Button, Message, Form, Dimmer, Loader, Header } from 'semantic-ui-react'; import { Button, Dimmer, Form, Header, Loader, Message } from 'semantic-ui-react';
import { drizzle } from '../index'; import { drizzle } from '../index';
import { createDatabases } from '../utils/orbitUtils'; import { createDatabases } from '../utils/orbitUtils';
import { updateUsername } from '../redux/actions/transactionsActions'; import { updateUsername } from '../redux/actions/transactionsActions';
const contract = "Forum"; const contract = 'Forum';
const checkUsernameTakenMethod = "isUserNameTaken"; const checkUsernameTakenMethod = 'isUserNameTaken';
const signUpMethod = "signUp"; const signUpMethod = 'signUp';
class UsernameFormContainer extends Component { class UsernameFormContainer extends Component {
constructor(props) { constructor(props) {
@ -23,8 +23,8 @@ class UsernameFormContainer extends Component {
this.state = { this.state = {
usernameInput: '', usernameInput: '',
error: false, error: false,
errorHeader: "", errorHeader: '',
errorMessage: "", errorMessage: '',
signingUp: false signingUp: false
}; };
} }
@ -36,38 +36,45 @@ class UsernameFormContainer extends Component {
}); });
if (value !== '') { if (value !== '') {
if (this.checkedUsernames.length > 0) { if (this.checkedUsernames.length > 0) {
if (this.checkedUsernames.some(e => e.usernameChecked === value)){ if (this.checkedUsernames.some(e => e.usernameChecked === value)) {
return; return;
} }
} }
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(value); drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(
value,
);
} }
} }
handleSubmit() { handleSubmit() {
if (this.state.usernameInput === ''){ if (this.state.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 (!this.state.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(e => e.usernameChecked === this.state.usernameInput)){ if (this.checkedUsernames.some(
e => e.usernameChecked === this.state.usernameInput,
)) {
this.completeAction(); this.completeAction();
} }
} }
} }
async completeAction() { async completeAction() {
if(this.props.user.hasSignedUp){ if (this.props.user.hasSignedUp) {
this.props.dispatch(updateUsername(...[this.state.usernameInput], null)); this.props.dispatch(updateUsername(...[this.state.usernameInput], null));
} else { } else {
this.setState({ signingUp: true }); this.setState({
signingUp: true
});
const orbitdbInfo = await createDatabases(); const orbitdbInfo = await createDatabases();
this.stackId = drizzle.contracts[contract].methods[signUpMethod] this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend(
.cacheSend(...[this.state.usernameInput, ...[
this.state.usernameInput,
orbitdbInfo.identityId, orbitdbInfo.identityId,
orbitdbInfo.identityPublicKey, orbitdbInfo.identityPublicKey,
orbitdbInfo.identityPrivateKey, orbitdbInfo.identityPrivateKey,
@ -76,50 +83,63 @@ class UsernameFormContainer extends Component {
orbitdbInfo.orbitPrivateKey, orbitdbInfo.orbitPrivateKey,
orbitdbInfo.topicsDB, orbitdbInfo.topicsDB,
orbitdbInfo.postsDB orbitdbInfo.postsDB
], { from: this.props.account}); ], {
from: this.props.account
},
);
} }
this.setState({ usernameInput: '' }); this.setState({
usernameInput: ''
});
} }
componentDidUpdate() { componentDidUpdate() {
if (this.state.signingUp) { if (this.state.signingUp) {
const txHash = this.props.transactionStack[this.stackId]; const txHash = this.props.transactionStack[this.stackId];
if (txHash && if (txHash
this.props.transactions[txHash] && && this.props.transactions[txHash]
this.props.transactions[txHash].status === "error") { && this.props.transactions[txHash].status === 'error') {
this.setState({signingUp: false}); this.setState({
signingUp: false
});
} }
} else { } else {
const temp = Object.values(this.props.contracts[contract][checkUsernameTakenMethod]); const temp = Object.values(
this.checkedUsernames = temp.map(checked => {return { this.props.contracts[contract][checkUsernameTakenMethod],
);
this.checkedUsernames = temp.map(checked => ({
usernameChecked: checked.args[0], usernameChecked: checked.args[0],
isTaken: checked.value isTaken: checked.value
}}); }));
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 === this.state.usernameInput
checked.isTaken && !this.state.error) { && checked.isTaken && !this.state.error) {
this.setState({ this.setState({
error: true, error: true,
errorHeader: "Data disapproved", errorHeader: 'Data disapproved',
errorMessage: "This username is already taken" errorMessage: 'This username is already taken'
}); });
} }
}) });
} }
} }
} }
render() { render() {
const hasSignedUp = this.props.user.hasSignedUp; const { hasSignedUp } = this.props.user;
if(hasSignedUp !== null) { if (hasSignedUp !== null) {
const buttonText = hasSignedUp ? "Update" : "Sign Up"; const buttonText = hasSignedUp ? 'Update' : 'Sign Up';
const placeholderText = hasSignedUp ? this.props.user.username : "Username"; const placeholderText = hasSignedUp
const withError = this.state.error && {error: true}; ? this.props.user.username
: 'Username';
const withError = this.state.error && {
error: true
};
/*var disableSubmit = true; /* var disableSubmit = true;
if (this.checkedUsernames.length > 0) { if (this.checkedUsernames.length > 0) {
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){ if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){
disableSubmit = false; disableSubmit = false;
@ -128,16 +148,16 @@ class UsernameFormContainer extends Component {
disableSubmit = false; disableSubmit = false;
} }
disableSubmit = (disableSubmit || this.state.error) && {loading: true};*/ disableSubmit = (disableSubmit || this.state.error) && {loading: true}; */
return( return (
<div> <div>
<Form onSubmit={this.handleSubmit} {...withError}> <Form onSubmit={this.handleSubmit} {...withError}>
<Form.Field required> <Form.Field required>
<label>Username</label> <label>Username</label>
<Form.Input <Form.Input
placeholder={placeholderText} placeholder={placeholderText}
name='usernameInput' name="usernameInput"
value={this.state.usernameInput} value={this.state.usernameInput}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -147,29 +167,30 @@ class UsernameFormContainer extends Component {
header={this.state.errorHeader} header={this.state.errorHeader}
content={this.state.errorMessage} content={this.state.errorMessage}
/> />
<Button type='submit'>{buttonText}</Button> <Button type="submit">{buttonText}</Button>
</Form> </Form>
<Dimmer active={this.state.signingUp} page> <Dimmer active={this.state.signingUp} page>
<Header as='h2' inverted> <Header as="h2" inverted>
<Loader size='large'>Magic elves are processing your noble request.</Loader> <Loader size="large">
Magic elves are processing your noble
request.
</Loader>
</Header> </Header>
</Dimmer> </Dimmer>
</div> </div>
); );
} }
return(null); return (null);
} }
} }
const mapStateToProps = state => { const mapStateToProps = state => ({
return {
account: state.accounts[0], account: state.accounts[0],
contracts: state.contracts, contracts: state.contracts,
transactions: state.transactions, transactions: state.transactions,
transactionStack: state.transactionStack, transactionStack: state.transactionStack,
user: state.user user: state.user
} });
};
export default connect(mapStateToProps)(UsernameFormContainer); export default connect(mapStateToProps)(UsernameFormContainer);

16
app/src/helpers/EpochTimeConverter.js

@ -1,12 +1,12 @@
const epochTimeConverter = (timestamp) => { const epochTimeConverter = (timestamp) => {
var timestampDate = new Date(0); const timestampDate = new Date(0);
timestampDate.setUTCSeconds(timestamp); timestampDate.setUTCSeconds(timestamp);
return ((timestampDate.getMonth() + 1) + " " return (`${timestampDate.getMonth() + 1} ${
+ timestampDate.getDate() + ", " timestampDate.getDate()}, ${
+ timestampDate.getFullYear() + ", " timestampDate.getFullYear()}, ${
+ timestampDate.getHours() + ":" timestampDate.getHours()}:${
+ timestampDate.getMinutes() + ":" timestampDate.getMinutes()}:${
+ timestampDate.getSeconds()) timestampDate.getSeconds()}`);
} };
export default epochTimeConverter; export default epochTimeConverter;

14
app/src/index.js

@ -1,16 +1,16 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router' import { ConnectedRouter } from 'connected-react-router';
import { Drizzle } from 'drizzle'; import { Drizzle } from 'drizzle';
import store, {history} from './redux/store'; import store, { history } from './redux/store';
import routes from './router/routes' import routes from './router/routes';
import { initIPFS } from './utils/orbitUtils' import { initIPFS } from './utils/orbitUtils';
import * as serviceWorker from './utils/serviceWorker'; import * as serviceWorker from './utils/serviceWorker';
import './assets/css/index.css'; import './assets/css/index.css';
import drizzleOptions from "./config/drizzleOptions"; import drizzleOptions from './config/drizzleOptions';
initIPFS(); initIPFS();
@ -21,10 +21,10 @@ export { drizzle };
render( render(
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
{ routes } {routes}
</ConnectedRouter> </ConnectedRouter>
</Provider>, </Provider>,
document.getElementById('root') document.getElementById('root'),
); );
serviceWorker.unregister(); // See also: http://bit.ly/CRA-PWA serviceWorker.unregister(); // See also: http://bit.ly/CRA-PWA

8
app/src/redux/actions/orbitActions.js

@ -3,7 +3,7 @@ const DATABASES_CREATED = 'DATABASES_CREATED';
const DATABASES_LOADED = 'DATABASES_LOADED'; const DATABASES_LOADED = 'DATABASES_LOADED';
const DATABASES_NOT_READY = 'DATABASES_NOT_READY'; const DATABASES_NOT_READY = 'DATABASES_NOT_READY';
function updateDatabases(type, orbitdb, topicsDB, postsDB){ function updateDatabases(type, orbitdb, topicsDB, postsDB) {
return { return {
type, type,
orbitdb, orbitdb,
@ -13,4 +13,8 @@ function updateDatabases(type, orbitdb, topicsDB, postsDB){
}; };
} }
export { DATABASES_CREATED, DATABASES_LOADED, DATABASES_NOT_READY, IPFS_INITIALIZED, updateDatabases } export { DATABASES_CREATED,
DATABASES_LOADED,
DATABASES_NOT_READY,
IPFS_INITIALIZED,
updateDatabases };

16
app/src/redux/actions/transactionsActions.js

@ -1,9 +1,9 @@
//Action creators // Action creators
export const INIT_TRANSACTION = 'INIT_TRANSACTION'; export const INIT_TRANSACTION = 'INIT_TRANSACTION';
export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION'; export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION';
export function updateUsername(newUsername, callback){ export function updateUsername(newUsername, callback) {
return { return {
type: INIT_TRANSACTION, type: INIT_TRANSACTION,
transactionDescriptor: transactionDescriptor:
@ -13,11 +13,11 @@ export function updateUsername(newUsername, callback){
params: [newUsername], params: [newUsername],
event: 'UsernameUpdated' event: 'UsernameUpdated'
}, },
callback: callback callback
}; };
} }
export function createTopic(userInputs){ export function createTopic(userInputs) {
return { return {
type: INIT_TRANSACTION, type: INIT_TRANSACTION,
transactionDescriptor: transactionDescriptor:
@ -27,11 +27,11 @@ export function createTopic(userInputs){
params: [], params: [],
event: 'TopicCreated' event: 'TopicCreated'
}, },
userInputs: userInputs userInputs
}; };
} }
export function createPost(topicID, userInputs){ export function createPost(topicID, userInputs) {
return { return {
type: INIT_TRANSACTION, type: INIT_TRANSACTION,
transactionDescriptor: transactionDescriptor:
@ -41,11 +41,11 @@ export function createPost(topicID, userInputs){
params: [topicID], params: [topicID],
event: 'PostCreated' event: 'PostCreated'
}, },
userInputs: userInputs userInputs
}; };
} }
export function updateTransaction(transactionIndex, updateDescriptor){ export function updateTransaction(transactionIndex, updateDescriptor) {
return { return {
type: UPDATE_TRANSACTION, type: UPDATE_TRANSACTION,
transactionUpdates: updateDescriptor transactionUpdates: updateDescriptor

4
app/src/redux/actions/userInterfaceActions.js

@ -1,8 +1,8 @@
//Action creators // Action creators
export const SET_NAVBAR_TITLE = 'SET_NAVBAR_TITLE'; export const SET_NAVBAR_TITLE = 'SET_NAVBAR_TITLE';
export function setNavBarTitle(newTitle){ export function setNavBarTitle(newTitle) {
return { return {
type: SET_NAVBAR_TITLE, type: SET_NAVBAR_TITLE,
title: newTitle title: newTitle

7
app/src/redux/reducers/orbitReducer.js

@ -1,4 +1,7 @@
import { IPFS_INITIALIZED, DATABASES_CREATED, DATABASES_LOADED, DATABASES_NOT_READY } from "../actions/orbitActions"; import { DATABASES_CREATED,
DATABASES_LOADED,
DATABASES_NOT_READY,
IPFS_INITIALIZED } from '../actions/orbitActions';
const initialState = { const initialState = {
ipfs: null, ipfs: null,
@ -46,7 +49,7 @@ const orbitReducer = (state = initialState, action) => {
id: null id: null
}; };
default: default:
return state return state;
} }
}; };

6
app/src/redux/reducers/rootReducer.js

@ -1,14 +1,14 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { drizzleReducers } from 'drizzle'; import { drizzleReducers } from 'drizzle';
import { connectRouter } from 'connected-react-router' import { connectRouter } from 'connected-react-router';
import userReducer from './userReducer'; import userReducer from './userReducer';
import orbitReducer from './orbitReducer'; import orbitReducer from './orbitReducer';
import userInterfaceReducer from './userInterfaceReducer'; import userInterfaceReducer from './userInterfaceReducer';
export default (history) => combineReducers({ export default history => combineReducers({
router: connectRouter(history), router: connectRouter(history),
user: userReducer, user: userReducer,
orbit: orbitReducer, orbit: orbitReducer,
interface: userInterfaceReducer, interface: userInterfaceReducer,
...drizzleReducers ...drizzleReducers
}) });

10
app/src/redux/reducers/userReducer.js

@ -1,7 +1,7 @@
const initialState = { const initialState = {
username: "", username: '',
address: "0x0", address: '0x0',
avatarUrl: "", avatarUrl: '',
hasSignedUp: null hasSignedUp: null
}; };
@ -15,12 +15,12 @@ const userReducer = (state = initialState, action) => {
}; };
case 'USER_DATA_UPDATED_(GUEST)': case 'USER_DATA_UPDATED_(GUEST)':
return { return {
username: "", username: '',
address: action.address, address: action.address,
hasSignedUp: false hasSignedUp: false
}; };
default: default:
return state return state;
} }
}; };

35
app/src/redux/sagas/drizzleUtilsSaga.js

@ -1,38 +1,37 @@
import { getContractInstance, getWeb3 } from "../../utils/drizzleUtils"; import { call, put, select, takeLatest } from 'redux-saga/effects';
import { call, put, takeLatest, select } from 'redux-saga/effects' import { getContractInstance, getWeb3 } from '../../utils/drizzleUtils';
import Forum from '../../contracts/Forum'; import Forum from '../../contracts/Forum';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from "../actions/drizzleUtilsActions"; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
const accounts = state => state.accounts;
const accounts = (state) => state.accounts; let initFlag; let web3; let
let initFlag, web3, contract; contract;
function* init() { function* init() {
if(!initFlag) { if (!initFlag) {
web3 = yield call(getWeb3); web3 = yield call(getWeb3);
contract = yield call(getContractInstance,{ contract = yield call(getContractInstance, {
web3: web3, web3, artifact: Forum
artifact: Forum });
initFlag = true;
yield put({
type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]
}); });
initFlag=true; } else console.warn('Attempted to reinitialize drizzleUtilsSaga!');
yield put({type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]});
}
else
console.warn("Attempted to reinitialize drizzleUtilsSaga!");
} }
// If the method below proves to be problematic/ineffective (i.e. getting current account // If the method below proves to be problematic/ineffective (i.e. getting current account
// from state), consider getting it from @drizzle-utils/get-accounts instead // from state), consider getting it from @drizzle-utils/get-accounts instead
// with (yield call(getAccounts, {web3}))[0]; // with (yield call(getAccounts, {web3}))[0];
function* getCurrentAccount(){ function* getCurrentAccount() {
return (yield select(accounts))[0]; return (yield select(accounts))[0];
} }
function* drizzleUtilsSaga() { function* drizzleUtilsSaga() {
yield takeLatest("DRIZZLE_INITIALIZED", init); yield takeLatest('DRIZZLE_INITIALIZED', init);
} }
export { web3, contract, getCurrentAccount } export { web3, contract, getCurrentAccount };
export default drizzleUtilsSaga; export default drizzleUtilsSaga;

60
app/src/redux/sagas/orbitSaga.js

@ -1,36 +1,52 @@
import {all, call, put, take, takeLatest} from 'redux-saga/effects' import { all, call, put, take, takeLatest } from 'redux-saga/effects';
import { contract, getCurrentAccount} from './drizzleUtilsSaga'; import { contract, getCurrentAccount } from './drizzleUtilsSaga';
import { loadDatabases } from '../../utils/orbitUtils' import { loadDatabases } from '../../utils/orbitUtils';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
import { IPFS_INITIALIZED, DATABASES_NOT_READY } from '../actions/orbitActions'; import { DATABASES_NOT_READY, IPFS_INITIALIZED } from '../actions/orbitActions';
let latestAccount; let latestAccount;
function* getOrbitDBInfo() { function* getOrbitDBInfo() {
yield put({type: 'ORRBIT_GETTING_INFO', ...[]}); yield put({
type: 'ORRBIT_GETTING_INFO', ...[]
});
const account = yield call(getCurrentAccount); const account = yield call(getCurrentAccount);
if(account!==latestAccount) { if (account !== latestAccount) {
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]); const txObj1 = yield call(contract.methods.hasUserSignedUp,
...[account]);
try { try {
const callResult = yield call(txObj1.call, {address:account}); const callResult = yield call(txObj1.call, {
if(callResult) { address: account
});
if (callResult) {
// console.log("Deleting local storage.."); // console.log("Deleting local storage..");
// localStorage.clear(); // localStorage.clear();
const txObj2 = yield call(contract.methods["getOrbitIdentityInfo"], ...[account]); const txObj2 = yield call(contract.methods.getOrbitIdentityInfo,
const orbitIdentityInfo = yield call(txObj2.call, {address: account}); ...[account]);
const txObj3 = yield call(contract.methods["getOrbitDBInfo"], ...[account]); const orbitIdentityInfo = yield call(txObj2.call, {
const orbitDBInfo = yield call(txObj3.call, {address: account}); address: account
yield call(loadDatabases, orbitIdentityInfo[0], orbitIdentityInfo[1], orbitIdentityInfo[2], });
orbitDBInfo[0], orbitDBInfo[1], orbitDBInfo[2], orbitDBInfo[3], orbitDBInfo[4]); const txObj3 = yield call(contract.methods.getOrbitDBInfo,
...[account]);
const orbitDBInfo = yield call(txObj3.call, {
address: account
});
yield call(loadDatabases, orbitIdentityInfo[0], orbitIdentityInfo[1],
orbitIdentityInfo[2],
orbitDBInfo[0], orbitDBInfo[1], orbitDBInfo[2], orbitDBInfo[3],
orbitDBInfo[4]);
} else {
yield put({
type: DATABASES_NOT_READY, ...[]
});
} }
else
yield put({type: DATABASES_NOT_READY, ...[]});
latestAccount=account; latestAccount = account;
} } catch (error) {
catch (error) {
console.error(error); console.error(error);
yield put({type: 'ORBIT_SAGA_ERROR', ...[]}); yield put({
type: 'ORBIT_SAGA_ERROR', ...[]
});
} }
} }
} }
@ -40,7 +56,7 @@ function* orbitSaga() {
take(DRIZZLE_UTILS_SAGA_INITIALIZED), take(DRIZZLE_UTILS_SAGA_INITIALIZED),
take(IPFS_INITIALIZED) take(IPFS_INITIALIZED)
]); ]);
yield takeLatest("ACCOUNT_CHANGED", getOrbitDBInfo); yield takeLatest('ACCOUNT_CHANGED', getOrbitDBInfo);
} }
export default orbitSaga; export default orbitSaga;

21
app/src/redux/sagas/rootSaga.js

@ -1,13 +1,18 @@
import { all, fork } from 'redux-saga/effects' import { all, fork } from 'redux-saga/effects';
import { drizzleSagas } from 'drizzle' import { drizzleSagas } from 'drizzle';
import drizzleUtilsSaga from './drizzleUtilsSaga' import drizzleUtilsSaga from './drizzleUtilsSaga';
import userSaga from './userSaga'; import userSaga from './userSaga';
import orbitSaga from "./orbitSaga"; import orbitSaga from './orbitSaga';
import transactionsSaga from "./transactionsSaga"; import transactionsSaga from './transactionsSaga';
export default function* root() { export default function* root() {
let sagas = [...drizzleSagas, drizzleUtilsSaga, orbitSaga, userSaga, transactionsSaga]; const sagas = [
...drizzleSagas,
drizzleUtilsSaga,
orbitSaga,
userSaga,
transactionsSaga];
yield all( yield all(
sagas.map(saga => fork(saga)) sagas.map(saga => fork(saga)),
) );
} }

82
app/src/redux/sagas/transactionsSaga.js

@ -1,73 +1,81 @@
import {call, select, take, takeEvery} from 'redux-saga/effects' import { call, select, take, takeEvery } from 'redux-saga/effects';
import { drizzle } from '../../index' import { drizzle } from '../../index';
import { orbitSagaPut } from '../../utils/orbitUtils' import { orbitSagaPut } from '../../utils/orbitUtils';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
let transactionsHistory = Object.create(null); const transactionsHistory = Object.create(null);
function* initTransaction(action) { function* initTransaction(action) {
var dataKey = drizzle.contracts[action.transactionDescriptor.contract] const dataKey = drizzle.contracts[action.transactionDescriptor.contract].methods[action.transactionDescriptor.method].cacheSend(
.methods[action.transactionDescriptor['method']] ...(action.transactionDescriptor.params),
.cacheSend(...(action.transactionDescriptor.params)); );
transactionsHistory[dataKey] = action; transactionsHistory[dataKey] = action;
transactionsHistory[dataKey].state = 'initialized'; transactionsHistory[dataKey].state = 'initialized';
} }
function* handleEvent(action) { function* handleEvent(action) {
var transactionStack = yield select((state) => state.transactionStack); const transactionStack = yield select(state => state.transactionStack);
var dataKey = transactionStack.indexOf(action.event.transactionHash); const dataKey = transactionStack.indexOf(action.event.transactionHash);
switch(action.event.event) { switch (action.event.event) {
case 'TopicCreated': case 'TopicCreated':
if (dataKey !== -1 && if (dataKey !== -1
transactionsHistory[dataKey] && && transactionsHistory[dataKey]
transactionsHistory[dataKey].state === 'initialized') { && transactionsHistory[dataKey].state === 'initialized') {
transactionsHistory[dataKey].state = 'success'; transactionsHistory[dataKey].state = 'success';
//Gets orbit // Gets orbit
const orbit = yield select((state) => state.orbit); const orbit = yield select(state => state.orbit);
//And saves the topic // And saves the topic
yield call(orbitSagaPut, orbit.topicsDB, action.event.returnValues.topicID, yield call(orbitSagaPut, orbit.topicsDB,
{ subject: transactionsHistory[dataKey].userInputs.topicSubject }); action.event.returnValues.topicID,
yield call(orbitSagaPut, orbit.postsDB, action.event.returnValues.postID, {
{ subject: transactionsHistory[dataKey].userInputs.topicSubject, subject: transactionsHistory[dataKey].userInputs.topicSubject
content: transactionsHistory[dataKey].userInputs.topicMessage }); });
yield call(orbitSagaPut, orbit.postsDB,
action.event.returnValues.postID,
{
subject: transactionsHistory[dataKey].userInputs.topicSubject,
content: transactionsHistory[dataKey].userInputs.topicMessage
});
} }
break; break;
case 'PostCreated': case 'PostCreated':
if (dataKey !== -1 && if (dataKey !== -1
transactionsHistory[dataKey] && && transactionsHistory[dataKey]
transactionsHistory[dataKey].state === 'initialized') { && transactionsHistory[dataKey].state === 'initialized') {
transactionsHistory[dataKey].state = 'success'; transactionsHistory[dataKey].state = 'success';
//Gets orbit // Gets orbit
const orbit = yield select((state) => state.orbit); const orbit = yield select(state => state.orbit);
//And saves the topic // And saves the topic
yield call(orbitSagaPut, orbit.postsDB, action.event.returnValues.postID, yield call(orbitSagaPut, orbit.postsDB,
{subject: transactionsHistory[dataKey].userInputs.postSubject, action.event.returnValues.postID,
content: transactionsHistory[dataKey].userInputs.postMessage }); {
subject: transactionsHistory[dataKey].userInputs.postSubject,
content: transactionsHistory[dataKey].userInputs.postMessage
});
} }
break; break;
default: default:
//Nothing to do here // Nothing to do here
return;
} }
} }
function* handleError() { function* handleError() {
var transactionStack = yield select((state) => state.transactionStack); const transactionStack = yield select(state => state.transactionStack);
transactionStack.forEach((transaction, index) => { transactionStack.forEach((transaction, index) => {
if (transaction.startsWith('TEMP_')) { if (transaction.startsWith('TEMP_')) {
transactionsHistory[index].state = 'error'; transactionsHistory[index].state = 'error';
} }
}) });
} }
function* transactionsSaga() { function* transactionsSaga() {
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); yield take(DRIZZLE_UTILS_SAGA_INITIALIZED);
yield takeEvery("INIT_TRANSACTION", initTransaction); yield takeEvery('INIT_TRANSACTION', initTransaction);
yield takeEvery("EVENT_FIRED", handleEvent); yield takeEvery('EVENT_FIRED', handleEvent);
yield takeEvery("TX_ERROR", handleError); yield takeEvery('TX_ERROR', handleError);
} }
export default transactionsSaga; export default transactionsSaga;

54
app/src/redux/sagas/userSaga.js

@ -1,53 +1,61 @@
import {call, put, select, take, takeEvery} from 'redux-saga/effects' import { call, put, select, take, takeEvery } from 'redux-saga/effects';
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; import { contract, getCurrentAccount } from './drizzleUtilsSaga';
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from "../actions/drizzleUtilsActions"; import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
let account; let account;
function* updateUserData() { function* updateUserData() {
const currentAccount = yield call(getCurrentAccount); const currentAccount = yield call(getCurrentAccount);
if(currentAccount!==account) { if (currentAccount !== account) {
account = currentAccount; account = currentAccount;
yield put({type: 'ACCOUNT_CHANGED', ...[]}); yield put({
type: 'ACCOUNT_CHANGED', ...[]
});
} }
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]); const txObj1 = yield call(contract.methods.hasUserSignedUp, ...[account]);
try { try {
const userState = yield call(getUserState); const userState = yield call(getUserState);
const callResult = yield call(txObj1.call, {address:account}); const callResult = yield call(txObj1.call, {
if(callResult) { address: account
const txObj2 = yield call(contract.methods["getUsername"], ...[account]); });
const username = yield call(txObj2.call, {address:account}); if (callResult) {
if(account!==userState.address || username!==userState.username){ const txObj2 = yield call(contract.methods.getUsername, ...[account]);
const username = yield call(txObj2.call, {
address: account
});
if (account !== userState.address || username !== userState.username) {
const dispatchArgs = { const dispatchArgs = {
address: account, address: account,
username: username username
}; };
yield put({type: 'USER_DATA_UPDATED_(AUTHENTICATED)', ...dispatchArgs}); yield put({
} type: 'USER_DATA_UPDATED_(AUTHENTICATED)', ...dispatchArgs
});
} }
else{ } else if (account !== userState.address) {
if(account!==userState.address){
const dispatchArgs = { const dispatchArgs = {
address: account address: account
}; };
yield put({type: 'USER_DATA_UPDATED_(GUEST)', ...dispatchArgs}); yield put({
} type: 'USER_DATA_UPDATED_(GUEST)', ...dispatchArgs
} });
} }
catch (error) { } catch (error) {
console.error(error); console.error(error);
yield put({type: 'USER_FETCHING_ERROR', ...[]}) yield put({
type: 'USER_FETCHING_ERROR', ...[]
});
} }
} }
function* getUserState(){ function* getUserState() {
return yield select((state) => state.user); return yield select(state => state.user);
} }
function* userSaga() { function* userSaga() {
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); yield take(DRIZZLE_UTILS_SAGA_INITIALIZED);
yield takeEvery("ACCOUNTS_FETCHED", updateUserData); yield takeEvery('ACCOUNTS_FETCHED', updateUserData);
} }
export default userSaga; export default userSaga;

19
app/src/redux/store.js

@ -1,30 +1,33 @@
import { createStore, applyMiddleware, compose } from 'redux'; import { applyMiddleware, compose, createStore } from 'redux';
import { createBrowserHistory } from 'history' import { createBrowserHistory } from 'history';
import createSagaMiddleware from 'redux-saga'; import createSagaMiddleware from 'redux-saga';
import {generateContractsInitialState} from 'drizzle'; import { generateContractsInitialState } from 'drizzle';
import {routerMiddleware} from 'connected-react-router'; import { routerMiddleware } from 'connected-react-router';
import rootSaga from './sagas/rootSaga'; import rootSaga from './sagas/rootSaga';
import drizzleOptions from '../config/drizzleOptions'; import drizzleOptions from '../config/drizzleOptions';
import createRootReducer from './reducers/rootReducer'; import createRootReducer from './reducers/rootReducer';
export const history = createBrowserHistory(); export const history = createBrowserHistory();
const rootReducer = createRootReducer(history); const rootReducer = createRootReducer(history);
const initialState = { contracts: generateContractsInitialState(drizzleOptions) }; const initialState = {
contracts: generateContractsInitialState(drizzleOptions)
};
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const sagaMiddleware = createSagaMiddleware(); const sagaMiddleware = createSagaMiddleware();
const routingMiddleware = routerMiddleware(history); const routingMiddleware = routerMiddleware(history);
const composedEnhancers = composeEnhancers(applyMiddleware(sagaMiddleware, routingMiddleware)); const composedEnhancers = composeEnhancers(
applyMiddleware(sagaMiddleware, routingMiddleware),
);
const store = createStore( const store = createStore(
rootReducer, rootReducer,
initialState, initialState,
composedEnhancers composedEnhancers,
); );
sagaMiddleware.run(rootSaga); sagaMiddleware.run(rootSaga);

25
app/src/router/PrivateRoute.js

@ -1,28 +1,27 @@
import React from 'react' import React from 'react';
import {connect} from 'react-redux'; import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom' import { Redirect, Route } from 'react-router-dom';
const PrivateRoute = ({ component: Component, ...rest }) => ( const PrivateRoute = ({ component: Component, ...rest }) => (
<Route <Route
{...rest} {...rest}
render={props => render={props => (props.hasSignedUp ? (
props.hasSignedUp ? (
<Component {...props} /> <Component {...props} />
) : ( ) : (
<Redirect to={{ <Redirect to={{
pathname: "/signup", pathname: '/signup',
state: { from: props.location } state: {
from: props.location
}
}} }}
/> />
) ))
} }
/> />
); );
const mapStateToProps = state => { const mapStateToProps = state => ({
return { hasSignedUp: state.user.hasSignedUp
hasSignedUp: state.user.hasSignedUp, });
}
};
export default connect(mapStateToProps)(PrivateRoute); export default connect(mapStateToProps)(PrivateRoute);

27
app/src/router/routes.js

@ -1,28 +1,31 @@
import React from 'react' import React from 'react';
import { Route, Redirect, Switch } from 'react-router-dom' import { Redirect, Route, Switch } from 'react-router-dom';
import CoreLayoutContainer from '../containers/CoreLayoutContainer'; import CoreLayoutContainer from '../containers/CoreLayoutContainer';
import HomeContainer from '../containers/HomeContainer' import HomeContainer from '../containers/HomeContainer';
import SignUpContainer from '../containers/SignUpContainer' import SignUpContainer from '../containers/SignUpContainer';
import StartTopicContainer from '../containers/StartTopicContainer' import StartTopicContainer from '../containers/StartTopicContainer';
import TopicContainer from '../containers/TopicContainer' import TopicContainer from '../containers/TopicContainer';
import ProfileContainer from '../containers/ProfileContainer' import ProfileContainer from '../containers/ProfileContainer';
import NotFound from '../components/NotFound' import NotFound from '../components/NotFound';
const routes = ( const routes = (
<div> <div>
<CoreLayoutContainer> <CoreLayoutContainer>
<Switch> <Switch>
<Route exact path="/" component={HomeContainer} /> <Route exact path="/" component={HomeContainer} />
<Redirect from='/home' to="/" /> <Redirect from="/home" to="/" />
<Route path="/signup" component={SignUpContainer} /> <Route path="/signup" component={SignUpContainer} />
<Route path="/startTopic" component={StartTopicContainer} /> <Route path="/startTopic" component={StartTopicContainer} />
<Route path="/topic/:topicId/:postId?" component={TopicContainer} /> <Route path="/topic/:topicId/:postId?" component={TopicContainer} />
<Route path='/profile/:address?/:username?' component={ProfileContainer} /> <Route
<Route path='/404' component={NotFound} /> path="/profile/:address?/:username?"
component={ProfileContainer}
/>
<Route path="/404" component={NotFound} />
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
</CoreLayoutContainer> </CoreLayoutContainer>
</div> </div>
); );
export default routes export default routes;

34
app/src/utils/drizzleUtils.js

@ -1,5 +1,5 @@
// See also: https://github.com/trufflesuite/drizzle-utils // See also: https://github.com/trufflesuite/drizzle-utils
const Web3 = require("web3"); const Web3 = require('web3');
const resolveWeb3 = (resolve, options, isBrowser) => { const resolveWeb3 = (resolve, options, isBrowser) => {
let provider; let provider;
@ -10,7 +10,7 @@ const resolveWeb3 = (resolve, options, isBrowser) => {
} else if (isBrowser && window.ethereum) { } else if (isBrowser && window.ethereum) {
// use `ethereum` object injected by MetaMask // use `ethereum` object injected by MetaMask
provider = window.ethereum; provider = window.ethereum;
} else if (isBrowser && typeof window.web3 !== "undefined") { } else if (isBrowser && typeof window.web3 !== 'undefined') {
// use injected web3 object by legacy dapp browsers // use injected web3 object by legacy dapp browsers
provider = window.web3.currentProvider; provider = window.web3.currentProvider;
} else if (options.fallbackProvider) { } else if (options.fallbackProvider) {
@ -18,38 +18,36 @@ const resolveWeb3 = (resolve, options, isBrowser) => {
provider = options.fallbackProvider; provider = options.fallbackProvider;
} else { } else {
// connect to development blockchain from `truffle develop` // connect to development blockchain from `truffle develop`
provider = new Web3.providers.HttpProvider("http://127.0.0.1:9545"); provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545');
} }
const web3 = new Web3(provider); const web3 = new Web3(provider);
resolve(web3); resolve(web3);
}; };
const getWeb3 = (options = {}) => const getWeb3 = (options = {
new Promise(resolve => { }) => new Promise((resolve) => {
// handle server-side and React Native environments // handle server-side and React Native environments
const isReactNative = const isReactNative = typeof navigator !== 'undefined' && navigator.product
typeof navigator !== "undefined" && navigator.product === "ReactNative"; === 'ReactNative';
const isNode = typeof window === "undefined"; const isNode = typeof window === 'undefined';
if (isNode || isReactNative) { if (isNode || isReactNative) {
return resolveWeb3(resolve, options, false); return resolveWeb3(resolve, options, false);
} }
// if page is ready, resolve for web3 immediately // if page is ready, resolve for web3 immediately
if (document.readyState === `complete`) { if (document.readyState === 'complete') {
return resolveWeb3(resolve, options, true); return resolveWeb3(resolve, options, true);
} }
// otherwise, resolve for web3 when page is done loading // otherwise, resolve for web3 when page is done loading
return window.addEventListener("load", () => return window.addEventListener('load', () => resolveWeb3(resolve, options, true));
resolveWeb3(resolve, options, true), });
);
});
const getContractInstance = (options = {}) => const getContractInstance = (options = {
new Promise(async (resolve, reject) => { }) => new Promise(async (resolve, reject) => {
if (!options.web3) { if (!options.web3) {
return reject(new Error("The options object with web3 is required.")); return reject(new Error('The options object with web3 is required.'));
} }
const { web3 } = options; const { web3 } = options;
@ -74,7 +72,7 @@ const getContractInstance = (options = {}) =>
} else { } else {
return reject( return reject(
new Error( new Error(
"You must pass in a contract artifact or the ABI of a deployed contract.", 'You must pass in a contract artifact or the ABI of a deployed contract.',
), ),
); );
} }
@ -83,6 +81,6 @@ const getContractInstance = (options = {}) =>
} catch (err) { } catch (err) {
return reject(err); return reject(err);
} }
}); });
export { getWeb3, getContractInstance }; export { getWeb3, getContractInstance };

44
app/src/utils/orbitUtils.js

@ -1,33 +1,37 @@
import OrbitDB from 'orbit-db'; import OrbitDB from 'orbit-db';
import Keystore from 'orbit-db-keystore'; import Keystore from 'orbit-db-keystore';
import path from 'path'; import path from 'path';
import IPFS from 'ipfs';
import store from '../redux/store'; import store from '../redux/store';
import { DATABASES_CREATED, DATABASES_LOADED, IPFS_INITIALIZED, updateDatabases } from '../redux/actions/orbitActions'; import { DATABASES_CREATED, DATABASES_LOADED, IPFS_INITIALIZED, updateDatabases } from '../redux/actions/orbitActions';
import IPFS from "ipfs"; import ipfsOptions from '../config/ipfsOptions';
import ipfsOptions from "../config/ipfsOptions";
function initIPFS(){ function initIPFS() {
const ipfs = new IPFS(ipfsOptions); const ipfs = new IPFS(ipfsOptions);
ipfs.on('ready', async () => { ipfs.on('ready', async () => {
store.dispatch({type: IPFS_INITIALIZED, ipfs}); store.dispatch({
console.log("IPFS initialized."); type: IPFS_INITIALIZED, ipfs
});
console.log('IPFS initialized.');
}); });
} }
async function createDatabases() { async function createDatabases() {
console.log("Creating databases..."); console.log('Creating databases...');
const ipfs = getIPFS(); const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs); const orbitdb = await new OrbitDB(ipfs);
const topicsDB = await orbitdb.keyvalue('topics'); const topicsDB = await orbitdb.keyvalue('topics');
const postsDB = await orbitdb.keyvalue('posts'); const postsDB = await orbitdb.keyvalue('posts');
store.dispatch(updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB)); store.dispatch(
updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB),
);
const orbitKey = orbitdb.keystore.getKey(orbitdb.id); const orbitKey = orbitdb.keystore.getKey(orbitdb.id);
return { return {
identityId: "Tempus", identityId: 'Tempus',
identityPublicKey: "edax", identityPublicKey: 'edax',
identityPrivateKey: "rerum", identityPrivateKey: 'rerum',
orbitId: orbitdb.id, orbitId: orbitdb.id,
orbitPublicKey: orbitKey.getPublic('hex'), orbitPublicKey: orbitKey.getPublic('hex'),
orbitPrivateKey: orbitKey.getPrivate('hex'), orbitPrivateKey: orbitKey.getPrivate('hex'),
@ -37,10 +41,11 @@ async function createDatabases() {
} }
async function loadDatabases(identityId, identityPublicKey, identityPrivateKey, async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
orbitId, orbitPublicKey, orbitPrivateKey, topicsDBId, postsDBId) { orbitId, orbitPublicKey, orbitPrivateKey,
console.log("Loading databases..."); topicsDBId, postsDBId) {
let directory = "./orbitdb"; console.log('Loading databases...');
let keystore = Keystore.create(path.join(directory, orbitId, '/keystore')); const directory = './orbitdb';
const keystore = Keystore.create(path.join(directory, orbitId, '/keystore'));
keystore._storage.setItem(orbitId, JSON.stringify({ keystore._storage.setItem(orbitId, JSON.stringify({
publicKey: orbitPublicKey, publicKey: orbitPublicKey,
@ -48,9 +53,12 @@ async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
})); }));
const ipfs = getIPFS(); const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs, directory, { peerId:orbitId, keystore:keystore}); const orbitdb = await new OrbitDB(ipfs, directory,
const topicsDB = await orbitdb.keyvalue('/orbitdb/' + topicsDBId +'/topics'); {
const postsDB = await orbitdb.keyvalue('/orbitdb/' + postsDBId +'/posts'); peerId: orbitId, keystore
});
const topicsDB = await orbitdb.keyvalue(`/orbitdb/${topicsDBId}/topics`);
const postsDB = await orbitdb.keyvalue(`/orbitdb/${postsDBId}/posts`);
await topicsDB.load(); await topicsDB.load();
await postsDB.load(); await postsDB.load();
@ -58,7 +66,7 @@ async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB)); store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB));
} }
function getIPFS(){ function getIPFS() {
return store.getState().orbit.ipfs; return store.getState().orbit.ipfs;
} }

41
app/src/utils/serviceWorker.js

@ -11,13 +11,13 @@
// opt-in, read http://bit.ly/CRA-PWA // opt-in, read http://bit.ly/CRA-PWA
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === 'localhost'
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || || window.location.hostname === '[::1]'
// 127.0.0.1/8 is considered localhost for IPv4. // 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match( || window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
) ),
); );
export function register(config) { export function register(config) {
@ -42,8 +42,8 @@ export function register(config) {
// service worker/PWA documentation. // service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
'This web app is being served cache-first by a service ' + 'This web app is being served cache-first by a service '
'worker. To learn more, visit http://bit.ly/CRA-PWA' + 'worker. To learn more, visit http://bit.ly/CRA-PWA',
); );
}); });
} else { } else {
@ -55,9 +55,7 @@ export function register(config) {
} }
function registerValidSW(swUrl, config) { function registerValidSW(swUrl, config) {
navigator.serviceWorker navigator.serviceWorker.register(swUrl).then((registration) => {
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => { registration.onupdatefound = () => {
const installingWorker = registration.installing; const installingWorker = registration.installing;
if (installingWorker == null) { if (installingWorker == null) {
@ -70,8 +68,8 @@ function registerValidSW(swUrl, config) {
// but the previous service worker will still serve the older // but the previous service worker will still serve the older
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
'New content is available and will be used when all ' + 'New content is available and will be used when all '
'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.',
); );
// Execute callback // Execute callback
@ -92,24 +90,22 @@ function registerValidSW(swUrl, config) {
} }
}; };
}; };
}) }).catch((error) => {
.catch(error => {
console.error('Error during service worker registration:', error); console.error('Error during service worker registration:', error);
}); });
} }
function checkValidServiceWorker(swUrl, config) { function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl) fetch(swUrl).then((response) => {
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type'); const contentType = response.headers.get('content-type');
if ( if (
response.status === 404 || response.status === 404
(contentType != null && contentType.indexOf('javascript') === -1) || (contentType != null && contentType.indexOf('javascript') === -1)
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => { registration.unregister().then(() => {
window.location.reload(); window.location.reload();
}); });
@ -118,17 +114,16 @@ function checkValidServiceWorker(swUrl, config) {
// Service worker found. Proceed as normal. // Service worker found. Proceed as normal.
registerValidSW(swUrl, config); registerValidSW(swUrl, config);
} }
}) }).catch(() => {
.catch(() => {
console.log( console.log(
'No internet connection found. App is running in offline mode.' 'No internet connection found. App is running in offline mode.',
); );
}); });
} }
export function unregister() { export function unregister() {
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister(); registration.unregister();
}); });
} }

7
package.json

@ -13,5 +13,12 @@
}, },
"dependencies": { "dependencies": {
"openzeppelin-solidity": "^2.1.2" "openzeppelin-solidity": "^2.1.2"
},
"devDependencies": {
"eslint": "5.15.1",
"eslint-config-airbnb": "17.1.0",
"eslint-plugin-import": "2.16.0",
"eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-react": "7.12.4"
} }
} }

14
truffle-config.js

@ -1,20 +1,20 @@
const path = require("path"); const path = require('path');
module.exports = { module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration> // See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration! // to customize your Truffle configuration!
contracts_build_directory: path.join(__dirname, "app/src/contracts"), contracts_build_directory: path.join(__dirname, 'app/src/contracts'),
networks: { networks: {
development: { development: {
host: "localhost", host: 'localhost',
port: 8545, port: 8545,
network_id: "*" // Match any network id network_id: '*' // Match any network id
} }
}, },
compilers:{ compilers: {
solc: { solc: {
version: "0.5.5", version: '0.5.5',
settings:{ settings: {
optimizer: { optimizer: {
enabled: true, enabled: true,
runs: 500 runs: 500

Loading…
Cancel
Save