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. 174
      app/src/assets/css/App.css
  7. 24
      app/src/assets/css/board-container.css
  8. 4
      app/src/assets/css/profile-container.css
  9. 14
      app/src/assets/css/sign-up-container.css
  10. 6
      app/src/assets/css/start-topic-container.css
  11. 50
      app/src/assets/css/topic-container.css
  12. 11742
      app/src/assets/fonts/fontawesome-free-5.7.2/all.js
  13. 20
      app/src/components/FloatingButton.js
  14. 27
      app/src/components/LoadingSpinner.js
  15. 349
      app/src/components/NewPost.js
  16. 109
      app/src/components/NewTopicPreview.js
  17. 17
      app/src/components/NotFound.js
  18. 431
      app/src/components/Post.js
  19. 120
      app/src/components/PostList.js
  20. 224
      app/src/components/ProfileInformation.js
  21. 213
      app/src/components/Topic.js
  22. 108
      app/src/components/TopicList.js
  23. 32
      app/src/config/drizzleOptions.js
  24. 24
      app/src/config/ipfsOptions.js
  25. 193
      app/src/containers/BoardContainer.js
  26. 47
      app/src/containers/CoreLayoutContainer.js
  27. 28
      app/src/containers/HomeContainer.js
  28. 88
      app/src/containers/NavBarContainer.js
  29. 323
      app/src/containers/ProfileContainer.js
  30. 67
      app/src/containers/SignUpContainer.js
  31. 241
      app/src/containers/StartTopicContainer.js
  32. 291
      app/src/containers/TopicContainer.js
  33. 221
      app/src/containers/TransactionsMonitorContainer.js
  34. 329
      app/src/containers/UsernameFormContainer.js
  35. 20
      app/src/helpers/EpochTimeConverter.js
  36. 22
      app/src/index.js
  37. 22
      app/src/redux/actions/orbitActions.js
  38. 86
      app/src/redux/actions/transactionsActions.js
  39. 12
      app/src/redux/actions/userInterfaceActions.js
  40. 93
      app/src/redux/reducers/orbitReducer.js
  41. 16
      app/src/redux/reducers/rootReducer.js
  42. 18
      app/src/redux/reducers/userInterfaceReducer.js
  43. 42
      app/src/redux/reducers/userReducer.js
  44. 41
      app/src/redux/sagas/drizzleUtilsSaga.js
  45. 82
      app/src/redux/sagas/orbitSaga.js
  46. 25
      app/src/redux/sagas/rootSaga.js
  47. 116
      app/src/redux/sagas/transactionsSaga.js
  48. 84
      app/src/redux/sagas/userSaga.js
  49. 25
      app/src/redux/store.js
  50. 43
      app/src/router/PrivateRoute.js
  51. 49
      app/src/router/routes.js
  52. 140
      app/src/utils/drizzleUtils.js
  53. 92
      app/src/utils/orbitUtils.js
  54. 129
      app/src/utils/serviceWorker.js
  55. 7
      package.json
  56. 40
      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",
"redux": "4.0.1",
"redux-saga": "0.16.2",
"semantic-ui-react": "0.85.0",
"semantic-ui-react": "0.86.0",
"uuid": "3.3.2",
"web3": "1.0.0-beta.48"
},
@ -35,9 +35,6 @@
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",

174
app/src/assets/css/App.css

@ -1,161 +1,161 @@
/* PAGE */
html, body {
margin: 0;
display: block;
height: 100%;
margin: 0;
display: block;
height: 100%;
}
strong {
font-weight: bold !important;
font-weight: bold !important;
}
#root {
height: 100%;
height: 100%;
}
.App {
width: 100%;
height: 100%;
margin: 0px;
display: flex;
flex-flow: column nowrap;
align-items: flex-start;
width: 100%;
height: 100%;
margin: 0px;
display: flex;
flex-flow: column nowrap;
align-items: flex-start;
}
.page-container {
width: 100%;
height: 100%;
margin: 71px 0px 0px;
width: 100%;
height: 100%;
margin: 71px 0px 0px;
}
.left-side-panel {
margin-top: 71px;
position: fixed;
width: 20%;
height: calc(100% - 71px);
top: 0;
left: 0;
margin-top: 71px;
position: fixed;
width: 20%;
height: calc(100% - 71px);
top: 0;
left: 0;
}
.main-panel {
width: 60%;
height: 100%;
margin: 0px 20%;
width: 60%;
height: 100%;
margin: 0px 20%;
}
.right-side-panel {
margin-top: 71px;
position: fixed;
width: 20%;
height: calc(100% - 71px);
top: 0;
right: 0;
margin-top: 71px;
position: fixed;
width: 20%;
height: calc(100% - 71px);
top: 0;
right: 0;
}
.sidebar-message {
margin: 0px 5px 12px 12px;
padding: 0px;
margin: 0px 5px 12px 12px;
padding: 0px;
}
.view-container {
width: 100%;
height: 100%;
margin: 0px auto;
width: 100%;
height: 100%;
margin: 0px auto;
}
/* MISC */
.navBarText {
height: 61px;
width: 1192px;
position: absolute;
left: calc(50% - 596px);
text-align: center;
z-index: -1; /* Temporary (?) */
height: 61px;
width: 1192px;
position: absolute;
left: calc(50% - 596px);
text-align: center;
z-index: -1; /* Temporary (?) */
}
.navBarText span {
color: #00b5ad;
height: 61px;
line-height: 61px;
vertical-align: middle;
font-size: 1.5em;
color: #00b5ad;
height: 61px;
line-height: 61px;
vertical-align: middle;
font-size: 1.5em;
}
.form-textarea-required {
color: rgb(159, 58, 56) !important;
outline-color: rgb(159, 58, 56) !important;
border-color: rgb(224, 180, 180) !important;
background-color: rgb(255, 246, 246) !important;
color: rgb(159, 58, 56) !important;
outline-color: rgb(159, 58, 56) !important;
border-color: rgb(224, 180, 180) !important;
background-color: rgb(255, 246, 246) !important;
}
.card {
width: 100% !important;
width: 100% !important;
}
.bottom-overlay-pad {
background: rgba(255, 255, 255, 0.85);
z-index: 10;
position: fixed;
bottom: 0px;
height: 62px;
width: 60%;
margin: 0px;
padding: 0px;
background: rgba(255, 255, 255, 0.85);
z-index: 10;
position: fixed;
bottom: 0px;
height: 62px;
width: 60%;
margin: 0px;
padding: 0px;
}
.action-button {
z-index: 11;
position: fixed;
bottom: 10px;
left: calc(50% - 24px);
z-index: 11;
position: fixed;
bottom: 10px;
left: calc(50% - 24px);
}
.grey-text {
color: grey;
color: grey;
}
.inline {
display: inline-block;
display: inline-block;
}
.no-margin {
margin: 0px;
margin: 0px;
}
hr {
color: #0c1a2b;
margin: 0px;
color: #0c1a2b;
margin: 0px;
}
*:focus {
outline:none !important
outline: none !important
}
a {
color:inherit;
text-decoration: none;
color: inherit;
text-decoration: none;
}
.center-in-parent {
width: 100%;
text-align: center;
width: 100%;
text-align: center;
}
.vertical-center-in-parent {
vertical-align: middle;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
vertical-align: middle;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.vertical-center-children {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
#overlay {
@ -167,21 +167,21 @@ a {
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
}
#overlay-content{
#overlay-content {
position: absolute;
text-align: center;
top: 50%;
left: 50%;
color: white;
transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
}
.fill {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
}

24
app/src/assets/css/board-container.css

@ -1,30 +1,30 @@
/* TOPICS LIST SCREEN */
.topics-list {
padding: 0px 2px;
margin-bottom: 75px;
padding: 0px 2px;
margin-bottom: 75px;
}
.topics-list a {
color: black !important;
text-decoration: none !important;
color: black !important;
text-decoration: none !important;
}
.topics-list a:hover {
color: black !important;
text-decoration: none !important;
color: black !important;
text-decoration: none !important;
}
.topic-subject {
margin: 0px 0px 5px;
margin: 0px 0px 5px;
}
.topic-meta {
margin: 5px 0px 0px;
margin: 5px 0px 0px;
}
.topic-date {
margin-bottom: 0px;
font-size: 0.77vw !important;
text-align: right;
}
margin-bottom: 0px;
font-size: 0.77vw !important;
text-align: right;
}

4
app/src/assets/css/profile-container.css

@ -1,5 +1,5 @@
/* PROFILE SCREEN */
.profile-tab {
width: 100%;
}
width: 100%;
}

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

@ -1,12 +1,12 @@
/* SIGN UP SCREEN */
.sign-up-container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.sign-up-container>div {
margin: auto;
}
.sign-up-container > div {
margin: auto;
}

6
app/src/assets/css/start-topic-container.css

@ -1,6 +1,6 @@
/* START TOPIC SCREEN */
.topic-form {
width: 100%;
margin: 20px 0px;
}
width: 100%;
margin: 20px 0px;
}

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

@ -1,51 +1,51 @@
/* POSTS LIST SCREEN */
.posts-list-spacer {
margin-bottom: 85px;
height: 0px;
margin-bottom: 85px;
height: 0px;
}
.post {
width: 100%;
background-color: #FFFFFF;
margin: 20px 0px;
padding: 0px;
width: 100%;
background-color: #FFFFFF;
margin: 20px 0px;
padding: 0px;
}
.post-meta {
float: right;
margin-right: 11.25px;
float: right;
margin-right: 11.25px;
}
.user-avatar {
width: 52px;
height: 52px;
text-align: center;
width: 52px;
height: 52px;
text-align: center;
}
.user-avatar a {
color: inherit !important;
text-decoration: none !important;
color: inherit !important;
text-decoration: none !important;
}
.stretch-space-between {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
.user-info {
background-color: #FFFFFF;
margin: 12px auto;
padding: 7px;
background-color: #FFFFFF;
margin: 12px auto;
padding: 7px;
}
.post-content a{
margin-top: 10px;
color: #039be5;
.post-content a {
margin-top: 10px;
color: #039be5;
}
.post-form {
width: 100%;
margin: 20px 0px;
}
width: 100%;
margin: 20px 0px;
}

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

File diff suppressed because one or more lines are too long

20
app/src/components/FloatingButton.js

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

27
app/src/components/LoadingSpinner.js

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

349
app/src/components/NewPost.js

@ -1,7 +1,7 @@
import React, { Component } from 'react';
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 UserAvatar from 'react-user-avatar';
@ -10,166 +10,201 @@ import ReactMarkdown from 'react-markdown';
import { createPost } from '../redux/actions/transactionsActions';
class NewPost extends Component {
constructor(props, context) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handlePreviewToggle = this.handlePreviewToggle.bind(this);
this.validateAndPost = this.validateAndPost.bind(this);
this.newPostOuterRef = React.createRef();
this.state = {
postSubjectInput: this.props.subject ? this.props.subject : "",
postContentInput: '',
postSubjectInputEmptySubmit: false,
postContentInputEmptySubmit: false,
previewEnabled: false,
previewDate: ''
};
constructor(props, context) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handlePreviewToggle = this.handlePreviewToggle.bind(this);
this.validateAndPost = this.validateAndPost.bind(this);
this.newPostOuterRef = React.createRef();
this.state = {
postSubjectInput: this.props.subject ? this.props.subject : '',
postContentInput: '',
postSubjectInputEmptySubmit: false,
postContentInputEmptySubmit: false,
previewEnabled: false,
previewDate: ''
};
}
async validateAndPost() {
if (this.state.postSubjectInput === '' || this.state.postContentInput
=== '') {
this.setState({
postSubjectInputEmptySubmit: this.state.postSubjectInput === '',
postContentInputEmptySubmit: this.state.postContentInput === ''
});
return;
}
async validateAndPost() {
if (this.state.postSubjectInput === '' || this.state.postContentInput === ''){
this.setState({
postSubjectInputEmptySubmit: this.state.postSubjectInput === '',
postContentInputEmptySubmit: this.state.postContentInput === ''
});
return;
}
this.props.dispatch(
createPost(this.props.topicID,
{
postSubject: this.state.postSubjectInput,
postMessage: this.state.postContentInput
}
)
);
this.props.onPostCreated();
}
handleInputChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handlePreviewToggle() {
this.setState((prevState, props) => ({
previewEnabled: !prevState.previewEnabled,
previewDate: this.getDate()
}));
}
getDate() {
const currentdate = new Date();
return ((currentdate.getMonth() + 1) + " "
+ currentdate.getDate() + ", "
+ currentdate.getFullYear() + ", "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds());
}
render() {
return (
<div className="post" ref={this.newPostOuterRef}>
<Divider horizontal>
<span className="grey-text">#{this.props.postIndex}</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<UserAvatar
size="52"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span><strong>{this.props.user.username}</strong></span>
<span className="grey-text">
{this.state.previewEnabled &&
<TimeAgo date={this.state.previewDate}/>
this.props.dispatch(
createPost(this.props.topicID,
{
postSubject: this.state.postSubjectInput,
postMessage: this.state.postContentInput
}),
);
this.props.onPostCreated();
}
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handlePreviewToggle() {
this.setState((prevState, props) => ({
previewEnabled: !prevState.previewEnabled,
previewDate: this.getDate()
}));
}
getDate() {
const currentdate = new Date();
return (`${currentdate.getMonth() + 1} ${
currentdate.getDate()}, ${
currentdate.getFullYear()}, ${
currentdate.getHours()}:${
currentdate.getMinutes()}:${
currentdate.getSeconds()}`);
}
render() {
return (
<div className="post" ref={this.newPostOuterRef}>
<Divider horizontal>
<span className="grey-text">
#
{this.props.postIndex}
</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<UserAvatar
size="52"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span><strong>{this.props.user.username}</strong></span>
<span className="grey-text">
{this.state.previewEnabled
&& <TimeAgo date={this.state.previewDate} />
}
</span>
</div>
<div className="stretch-space-between">
<span><strong>
{this.state.previewEnabled &&
("Subject: " + this.state.postSubjectInput)
</span>
</div>
<div className="stretch-space-between">
<span>
<strong>
{this.state.previewEnabled
&& (`Subject: ${
this.state.postSubjectInput}`)
}
</strong></span>
</div>
<div className="post-content">
<div style={{display: this.state.previewEnabled ? "block" : "none"}}>
<ReactMarkdown source={this.state.postContentInput}
className="markdown-preview" />
</div>
<Form className="topic-form">
<Form.Input key={"postSubjectInput"}
style={{display: this.state.previewEnabled ? "none" : ""}}
name={"postSubjectInput"}
error={this.state.postSubjectInputEmptySubmit}
type="text"
value={this.state.postSubjectInput}
placeholder="Subject"
id="postSubjectInput"
onChange={this.handleInputChange} />
<TextArea key={"postContentInput"}
style={{display: this.state.previewEnabled ? "none" : ""}}
name={"postContentInput"}
className={this.state.postContentInputEmptySubmit ? "form-textarea-required" : ""}
value={this.state.postContentInput}
placeholder="Post"
id="postContentInput"
onChange={this.handleInputChange}
rows={4} autoHeight />
<br/><br/>
<Button.Group>
<Button key="submit"
type="button"
onClick={this.validateAndPost}
color='teal'
animated>
<Button.Content visible>Post</Button.Content>
<Button.Content hidden>
<Icon name='reply' />
</Button.Content>
</Button>
<Button type="button"
onClick={this.handlePreviewToggle}
color='yellow'>
{this.state.previewEnabled ? "Edit" : "Preview"}
</Button>
<Button type="button"
onClick={this.props.onCancelClick}
color='red'>
Cancel
</Button>
</Button.Group>
</Form>
</div>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
}
componentDidMount(){
this.newPostOuterRef.current.scrollIntoView(true);
}
</strong>
</span>
</div>
<div className="post-content">
<div style={{
display: this.state.previewEnabled
? 'block'
: 'none'
}}
>
<ReactMarkdown
source={this.state.postContentInput}
className="markdown-preview"
/>
</div>
<Form className="topic-form">
<Form.Input
key="postSubjectInput"
style={{
display: this.state.previewEnabled
? 'none'
: ''
}}
name="postSubjectInput"
error={this.state.postSubjectInputEmptySubmit}
type="text"
value={this.state.postSubjectInput}
placeholder="Subject"
id="postSubjectInput"
onChange={this.handleInputChange}
/>
<TextArea
key="postContentInput"
style={{
display: this.state.previewEnabled
? 'none'
: ''
}}
name="postContentInput"
className={this.state.postContentInputEmptySubmit
? 'form-textarea-required'
: ''}
value={this.state.postContentInput}
placeholder="Post"
id="postContentInput"
onChange={this.handleInputChange}
rows={4}
autoHeight
/>
<br />
<br />
<Button.Group>
<Button
key="submit"
type="button"
onClick={this.validateAndPost}
color="teal"
animated
>
<Button.Content visible>Post</Button.Content>
<Button.Content hidden>
<Icon name="reply" />
</Button.Content>
</Button>
<Button
type="button"
onClick={this.handlePreviewToggle}
color="yellow"
>
{this.state.previewEnabled ? 'Edit' : 'Preview'}
</Button>
<Button
type="button"
onClick={this.props.onCancelClick}
color="red"
>
Cancel
</Button>
</Button.Group>
</Form>
</div>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
}
componentDidMount() {
this.newPostOuterRef.current.scrollIntoView(true);
}
}
const mapStateToProps = state => {
return {
orbitDB: state.orbitDB,
user: state.user
}
};
const mapStateToProps = state => ({
orbitDB: state.orbitDB,
user: state.user
});
export default connect(mapStateToProps)(NewPost);
export default connect(mapStateToProps)(NewPost);

109
app/src/components/NewTopicPreview.js

@ -1,65 +1,68 @@
import React, { Component } from 'react';
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 UserAvatar from 'react-user-avatar';
import ReactMarkdown from 'react-markdown';
class Post extends Component {
constructor(props, context) {
super(props);
}
constructor(props, context) {
super(props);
}
render(){
return (
<div className="post">
<Divider horizontal>
<span className="grey-text">#0</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<UserAvatar
size="52"
className="inline"
src={this.props.user.avatarUrl}
name={this.props.user.username}/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span>
<strong>
{this.props.user.username}
</strong>
</span>
<span className="grey-text">
<TimeAgo date={this.props.date}/>
</span>
</div>
<div className="stretch-space-between">
<span><strong>
Subject: {this.props.subject}
</strong></span>
</div>
<div className="post-content">
<ReactMarkdown source={this.props.content} />
</div>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
}
};
render() {
return (
<div className="post">
<Divider horizontal>
<span className="grey-text">#0</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<UserAvatar
size="52"
className="inline"
src={this.props.user.avatarUrl}
name={this.props.user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span>
<strong>
{this.props.user.username}
</strong>
</span>
<span className="grey-text">
<TimeAgo date={this.props.date} />
</span>
</div>
<div className="stretch-space-between">
<span>
<strong>
Subject:
{' '}
{this.props.subject}
</strong>
</span>
</div>
<div className="post-content">
<ReactMarkdown source={this.props.content} />
</div>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
}
}
const mapStateToProps = state => {
return {
user: state.user
}
};
const mapStateToProps = state => ({
user: state.user
});
export default connect(mapStateToProps)(Post);
export default connect(mapStateToProps)(Post);

17
app/src/components/NotFound.js

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

431
app/src/components/Post.js

@ -1,204 +1,289 @@
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { push } from 'connected-react-router'
import { push } from 'connected-react-router';
import { Link, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import ContentLoader from "react-content-loader"
import { Transition } from 'semantic-ui-react'
import { Grid, Divider, Button, Icon, Label } from 'semantic-ui-react'
import ContentLoader from 'react-content-loader';
import { Button, Divider, Grid, Icon, Label, Transition } from 'semantic-ui-react';
import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter';
import UserAvatar from 'react-user-avatar';
import ReactMarkdown from 'react-markdown';
import epochTimeConverter from '../helpers/EpochTimeConverter';
class Post extends Component {
constructor(props) {
super(props);
this.getBlockchainData = this.getBlockchainData.bind(this);
this.fetchPost = this.fetchPost.bind(this);
if (props.getFocus){
this.postRef = React.createRef();
}
this.state = {
fetchPostDataStatus: 'pending',
postContent: '',
postSubject: '',
readyForAnimation: false,
animateOnToggle: true
}
constructor(props) {
super(props);
this.getBlockchainData = this.getBlockchainData.bind(this);
this.fetchPost = this.fetchPost.bind(this);
if (props.getFocus) {
this.postRef = React.createRef();
}
getBlockchainData() {
if (this.props.postData &&
this.props.orbitDB.orbitdb &&
this.state.fetchPostDataStatus === "pending") {
this.setState({ fetchPostDataStatus: 'fetching' });
this.fetchPost(this.props.postID);
}
this.state = {
fetchPostDataStatus: 'pending',
postContent: '',
postSubject: '',
readyForAnimation: false,
animateOnToggle: true
};
}
getBlockchainData() {
if (this.props.postData
&& this.props.orbitDB.orbitdb
&& this.state.fetchPostDataStatus === 'pending') {
this.setState({
fetchPostDataStatus: 'fetching'
});
this.fetchPost(this.props.postID);
}
}
async fetchPost(postID) {
let orbitPostData;
if (this.props.postData.value[1] === this.props.user.address) {
orbitPostData = this.props.orbitDB.postsDB.get(postID);
} else {
const fullAddress = "/orbitdb/" + this.props.postData.value[0] + "/posts";
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
let localOrbitData = store.get(postID);
if (localOrbitData) {
orbitPostData = localOrbitData;
} else {
// Wait until we have received something from the network
store.events.on('replicated', () => {
orbitPostData = store.get(postID);
})
}
}
async fetchPost(postID) {
let orbitPostData;
if (this.props.postData.value[1] === this.props.user.address) {
orbitPostData = this.props.orbitDB.postsDB.get(postID);
} else {
const fullAddress = `/orbitdb/${this.props.postData.value[0]}/posts`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
this.setState({
postContent: orbitPostData.content,
postSubject: orbitPostData.subject,
fetchPostDataStatus: 'fetched',
readyForAnimation: true
const localOrbitData = store.get(postID);
if (localOrbitData) {
orbitPostData = localOrbitData;
} else {
// Wait until we have received something from the network
store.events.on('replicated', () => {
orbitPostData = store.get(postID);
});
}
}
render(){
let avatarView = (this.props.postData
? <UserAvatar
size="52"
className="inline"
src={this.props.avatarUrl}
name={this.props.postData.value[2]}/>
: <div className="user-avatar">
<ContentLoader height={52} width={52} speed={2}
primaryColor="#b2e8e6" secondaryColor="#00b5ad">
<circle cx="26" cy="26" r="26" />
</ContentLoader>
</div>
);
return (
<Transition animation='tada' duration={500} visible={this.state.animateOnToggle}>
<div className="post" ref={this.postRef ? this.postRef : null}>
<Divider horizontal>
<span className="grey-text">#{this.props.postIndex}</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
{this.props.postData !== null
?<Link to={"/profile/" + this.props.postData.value[1]
+ "/" + this.props.postData.value[2]}
onClick={(event) => {event.stopPropagation()}}>
{avatarView}
</Link>
:avatarView
}
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span className={this.props.postData !== null ? "" : "grey-text"}>
<strong>
{this.props.postData !== null
?this.props.postData.value[2]
:"Username"
this.setState({
postContent: orbitPostData.content,
postSubject: orbitPostData.subject,
fetchPostDataStatus: 'fetched',
readyForAnimation: true
});
}
render() {
const avatarView = (this.props.postData
? (
<UserAvatar
size="52"
className="inline"
src={this.props.avatarUrl}
name={this.props.postData.value[2]}
/>
)
: (
<div className="user-avatar">
<ContentLoader
height={52}
width={52}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<circle cx="26" cy="26" r="26" />
</ContentLoader>
</div>
)
);
return (
<Transition
animation="tada"
duration={500}
visible={this.state.animateOnToggle}
>
<div className="post" ref={this.postRef ? this.postRef : null}>
<Divider horizontal>
<span className="grey-text">
#
{this.props.postIndex}
</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
{this.props.postData !== null
? (
<Link
to={`/profile/${this.props.postData.value[1]
}/${this.props.postData.value[2]}`}
onClick={(event) => { event.stopPropagation(); }}
>
{avatarView}
</Link>
)
: avatarView
}
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span className={this.props.postData
!== null ? '' : 'grey-text'}
>
<strong>
{this.props.postData !== null
? this.props.postData.value[2]
: 'Username'
}
</strong>
</span>
<span className="grey-text">
{this.props.postData !== null &&
<TimeAgo date={epochTimeConverter(this.props.postData.value[3])}/>
</strong>
</span>
<span className="grey-text">
{this.props.postData !== null
&& (
<TimeAgo date={epochTimeConverter(
this.props.postData.value[3],
)}
/>
)
}
</span>
</div>
<div className="stretch-space-between">
<span className={this.state.postSubject === '' ? "" : "grey-text"}>
<strong>
{this.state.postSubject === ''
? <ContentLoader 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>
: 'Subject: ' + this.state.postSubject
</span>
</div>
<div className="stretch-space-between">
<span
className={this.state.postSubject
=== '' ? '' : 'grey-text'}
>
<strong>
{this.state.postSubject === ''
? (
<ContentLoader
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>
)
: `Subject: ${
this.state.postSubject}`
}
</strong>
</span>
</div>
<div className="post-content">
{this.state.postContent !== ''
? <ReactMarkdown source={this.state.postContent} />
: <ContentLoader height={11.2} 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>
}
</div>
</div>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column floated="right" textAlign="right">
<Button icon size='mini' style={{marginRight: "0px"}}>
<Icon name='chevron up' />
</Button>
<Label color="teal">8000</Label>
<Button icon size='mini'>
<Icon name='chevron down' />
</Button>
<Button icon size='mini'
onClick={this.props.postData
? () => { this.props.navigateTo("/topic/"
+ this.props.postData.value[4] + "/"
+ this.props.postID)}
: () => {}}>
<Icon name='linkify' />
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</strong>
</span>
</div>
<div className="post-content">
{this.state.postContent !== ''
? <ReactMarkdown source={this.state.postContent} />
: (
<ContentLoader
height={11.2}
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>
)
}
</div>
</div>
</Transition>
);
}
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column floated="right" textAlign="right">
<Button
icon
size="mini"
style={{
marginRight: '0px'
}}
>
<Icon name="chevron up" />
</Button>
<Label color="teal">8000</Label>
<Button icon size="mini">
<Icon name="chevron down" />
</Button>
<Button
icon
size="mini"
onClick={this.props.postData
? () => {
this.props.navigateTo(`/topic/${
this.props.postData.value[4]}/${
this.props.postID}`);
}
: () => {}}
>
<Icon name="linkify" />
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
</Transition>
);
}
componentDidMount() {
this.getBlockchainData();
}
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate(){
this.getBlockchainData();
if (this.state.readyForAnimation){
if (this.postRef){
setTimeout(() => {
this.postRef.current.scrollIntoView({ block: 'start', behavior: 'smooth' });
setTimeout(() => {
this.setState({ animateOnToggle: false });
}, 300);
}, 100);
this.setState({ readyForAnimation: false });
}
}
componentDidUpdate() {
this.getBlockchainData();
if (this.state.readyForAnimation) {
if (this.postRef) {
setTimeout(() => {
this.postRef.current.scrollIntoView(
{
block: 'start', behavior: 'smooth'
},
);
setTimeout(() => {
this.setState({
animateOnToggle: false
});
}, 300);
}, 100);
this.setState({
readyForAnimation: false
});
}
}
};
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location)
navigateTo: location => push(location)
}, dispatch);
const mapStateToProps = state => {
return {
user: state.user,
orbitDB: state.orbit
}
};
const mapStateToProps = state => ({
user: state.user,
orbitDB: state.orbit
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post));
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post));

120
app/src/components/PostList.js

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

224
app/src/components/ProfileInformation.js

@ -1,132 +1,144 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import UserAvatar from 'react-user-avatar';
import { drizzle } from '../index';
import UserAvatar from 'react-user-avatar';
import epochTimeConverter from '../helpers/EpochTimeConverter';
import UsernameFormContainer from '../containers/UsernameFormContainer';
const callsInfo = [{
contract: 'Forum',
method: 'getUserDateOfRegister'
},{
contract: 'Forum',
method: 'getOrbitDBId'
}]
const callsInfo = [
{
contract: 'Forum',
method: 'getUserDateOfRegister'
}, {
contract: 'Forum',
method: 'getOrbitDBId'
}];
class ProfileInformation extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.getBlockchainData = this.getBlockchainData.bind(this);
this.dataKey = [];
this.getBlockchainData = this.getBlockchainData.bind(this);
this.dataKey = [];
this.state = {
pageStatus: 'initialized',
dateOfRegister: '',
orbitDBId: ''
};
this.state = {
pageStatus: 'initialized',
dateOfRegister: '',
orbitDBId: ''
};
}
getBlockchainData() {
if (this.state.pageStatus === 'initialized'
&& this.props.drizzleStatus.initialized) {
callsInfo.forEach((call, index) => {
this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall(
this.props.address,
);
});
this.setState({
pageStatus: 'loading'
});
}
getBlockchainData(){
if (this.state.pageStatus === 'initialized' &&
this.props.drizzleStatus['initialized']) {
callsInfo.forEach((call, index) => {
this.dataKey[index] = drizzle.contracts[call.contract]
.methods[call.method].cacheCall(this.props.address);
})
this.setState({ pageStatus: 'loading' });
if (this.state.pageStatus === 'loading') {
let pageStatus = 'loaded';
callsInfo.forEach((call, index) => {
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) {
pageStatus = 'loading';
}
});
if (this.state.pageStatus === 'loading') {
var pageStatus = 'loaded';
callsInfo.forEach((call, index) => {
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) {
pageStatus = 'loading';
return;
}
})
if (pageStatus === 'loaded') {
this.setState({
pageStatus
});
}
}
if (pageStatus === 'loaded') {
this.setState({ pageStatus: pageStatus });
}
if (this.state.pageStatus === 'loaded') {
if (this.state.dateOfRegister === '') {
const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction) {
this.setState({
dateOfRegister: transaction.value
});
}
if (this.state.pageStatus === 'loaded'){
if (this.state.dateOfRegister === ''){
let transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction){
this.setState({ dateOfRegister: transaction.value });
}
}
if (this.state.orbitDBId === ''){
let transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction){
this.setState({ orbitDBId: transaction.value });
}
}
}
if (this.state.orbitDBId === '') {
const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction) {
this.setState({
orbitDBId: transaction.value
});
}
}
}
}
render() {
return (
<div className="user-info">
{this.props.avatarUrl && <UserAvatar
size="40"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.username}/>}
<table className="highlight centered responsive-table">
<tbody>
<tr>
<td><strong>Username:</strong></td>
<td>{this.props.username}</td>
</tr>
<tr>
<td><strong>Account address:</strong></td>
<td>{this.props.address}</td>
</tr>
<tr>
<td><strong>OrbitDB:</strong></td>
<td>{this.state.orbitDBId}</td>
</tr>
<tr>
<td><strong>Number of topics created:</strong></td>
<td>{this.props.numberOfTopics}</td>
</tr>
<tr>
<td><strong>Number of posts:</strong></td>
<td>{this.props.numberOfPosts}</td>
</tr>
{this.state.dateOfRegister &&
<tr>
<td><strong>Member since:</strong></td>
<td>{epochTimeConverter(this.state.dateOfRegister)}</td>
</tr>
}
</tbody>
</table>
{this.props.self && <UsernameFormContainer/>}
</div>
);
}
render() {
return (
<div className="user-info">
{this.props.avatarUrl && (
<UserAvatar
size="40"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.username}
/>
)}
<table className="highlight centered responsive-table">
<tbody>
<tr>
<td><strong>Username:</strong></td>
<td>{this.props.username}</td>
</tr>
<tr>
<td><strong>Account address:</strong></td>
<td>{this.props.address}</td>
</tr>
<tr>
<td><strong>OrbitDB:</strong></td>
<td>{this.state.orbitDBId}</td>
</tr>
<tr>
<td><strong>Number of topics created:</strong></td>
<td>{this.props.numberOfTopics}</td>
</tr>
<tr>
<td><strong>Number of posts:</strong></td>
<td>{this.props.numberOfPosts}</td>
</tr>
{this.state.dateOfRegister
&& (
<tr>
<td><strong>Member since:</strong></td>
<td>{epochTimeConverter(this.state.dateOfRegister)}</td>
</tr>
)
}
</tbody>
</table>
{this.props.self && <UsernameFormContainer />}
</div>
);
}
componentDidMount() {
this.getBlockchainData();
}
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate(){
this.getBlockchainData();
}
};
componentDidUpdate() {
this.getBlockchainData();
}
}
const mapStateToProps = state => {
return {
drizzleStatus: state.drizzleStatus,
contracts: state.contracts,
user: state.user
}
};
const mapStateToProps = state => ({
drizzleStatus: state.drizzleStatus,
contracts: state.contracts,
user: state.user
});
export default connect(mapStateToProps)(ProfileInformation);

213
app/src/components/Topic.js

@ -1,118 +1,139 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import { withRouter } from 'react-router-dom';
import ContentLoader from "react-content-loader"
import { Card } from 'semantic-ui-react'
import ContentLoader from 'react-content-loader';
import { Card } from 'semantic-ui-react';
import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter'
import epochTimeConverter from '../helpers/EpochTimeConverter';
class Topic extends Component {
constructor(props){
super(props);
constructor(props) {
super(props);
this.fetchSubject = this.fetchSubject.bind(this);
this.fetchSubject = this.fetchSubject.bind(this);
this.state = {
topicSubject: null,
topicSubjectFetchStatus: 'pending'
}
}
async fetchSubject(topicID) {
var topicSubject;
this.state = {
topicSubject: null,
topicSubjectFetchStatus: 'pending'
};
}
if (this.props.topicData.value[1] === this.props.user.address) {
let orbitData = this.props.orbitDB.topicsDB.get(topicID);
topicSubject = orbitData['subject']
} else {
const fullAddress = "/orbitdb/" + this.props.topicData.value[0] + "/topics";
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
async fetchSubject(topicID) {
let topicSubject;
let localOrbitData = store.get(topicID);
if (localOrbitData) {
topicSubject = localOrbitData['subject'];
} else {
// Wait until we have received something from the network
store.events.on('replicated', () => {
topicSubject = store.get(topicID)['subject'];
})
}
}
if (this.props.topicData.value[1] === this.props.user.address) {
const orbitData = this.props.orbitDB.topicsDB.get(topicID);
topicSubject = orbitData.subject;
} else {
const fullAddress = `/orbitdb/${this.props.topicData.value[0]
}/topics`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
this.setState({
topicSubject: topicSubject,
topicSubjectFetchStatus: 'fetched'
})
const localOrbitData = store.get(topicID);
if (localOrbitData) {
topicSubject = localOrbitData.subject;
} else {
// Wait until we have received something from the network
store.events.on('replicated', () => {
topicSubject = store.get(topicID).subject;
});
}
}
render(){
return (
<Card link className="card"
onClick={() => {this.props.history.push("/topic/" + this.props.topicID)}}>
<Card.Content>
<div className={"topic-subject" + (this.state.topicSubject ? "" : " grey-text")}>
<p><strong>
{this.state.topicSubject !== null ? this.state.topicSubject
:<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" />
</ContentLoader>}
</strong></p>
</div>
<hr/>
<div className="topic-meta">
<p className={"no-margin" +
(this.props.topicData !== null ? "" : " grey-text")}>
{this.props.topicData !== null
?this.props.topicData.value[2]
:"Username"
}
</p>
<p className={"no-margin" +
(this.props.topicData !== null ? "" : " grey-text")}>
{"Number of replies: " + (this.props.topicData !== null
?this.props.topicData.value[4].length
:"")
}
</p>
<p className="topic-date grey-text">
{this.props.topicData !== null &&
<TimeAgo date={epochTimeConverter(this.props.topicData.value[3])}/>
}
</p>
</div>
</Card.Content>
</Card>
);
}
this.setState({
topicSubject,
topicSubjectFetchStatus: 'fetched'
});
}
componentDidMount() {
if (this.props.topicData !== null &&
this.state.topicSubjectFetchStatus === "pending" &&
this.props.orbitDB.ipfsInitialized &&
this.props.orbitDB.orbitdb) {
this.fetchSubject(this.props.topicID);
}
}
render() {
return (
<Card
link
className="card"
onClick={() => {
this.props.history.push(`/topic/${this.props.topicID}`);
}}
>
<Card.Content>
<div className={`topic-subject${
this.state.topicSubject ? '' : ' grey-text'}`}
>
<p>
<strong>
{this.state.topicSubject !== null ? this.state.topicSubject
: (
<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" />
</ContentLoader>
)}
</strong>
</p>
</div>
<hr />
<div className="topic-meta">
<p className={`no-margin${
this.props.topicData !== null ? '' : ' grey-text'}`}
>
{this.props.topicData !== null
? this.props.topicData.value[2]
: 'Username'
}
</p>
<p className={`no-margin${
this.props.topicData !== null ? '' : ' grey-text'}`}
>
{`Number of replies: ${this.props.topicData !== null
? this.props.topicData.value[4].length
: ''}`
}
</p>
<p className="topic-date grey-text">
{this.props.topicData !== null
&& (
<TimeAgo
date={epochTimeConverter(this.props.topicData.value[3])}
/>
)
}
</p>
</div>
</Card.Content>
</Card>
);
}
componentDidUpdate() {
if (this.props.topicData !== null &&
this.state.topicSubjectFetchStatus === "pending" &&
this.props.orbitDB.ipfsInitialized &&
this.props.orbitDB.orbitdb) {
this.fetchSubject(this.props.topicID);
}
componentDidMount() {
if (this.props.topicData !== null
&& this.state.topicSubjectFetchStatus === 'pending'
&& this.props.orbitDB.ipfsInitialized
&& this.props.orbitDB.orbitdb) {
this.fetchSubject(this.props.topicID);
}
};
}
const mapStateToProps = state => {
return {
user: state.user,
orbitDB: state.orbit
componentDidUpdate() {
if (this.props.topicData !== null
&& this.state.topicSubjectFetchStatus === 'pending'
&& this.props.orbitDB.ipfsInitialized
&& this.props.orbitDB.orbitdb) {
this.fetchSubject(this.props.topicID);
}
}
}
const mapStateToProps = state => ({
user: state.user,
orbitDB: state.orbit
});
export default withRouter(connect(mapStateToProps)(Topic));

108
app/src/components/TopicList.js

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

32
app/src/config/drizzleOptions.js

@ -1,20 +1,20 @@
import Forum from "../contracts/Forum.json";
import Forum from '../contracts/Forum.json';
const drizzleOptions = {
web3: {
fallback: {
type: 'ws',
url: 'ws://127.0.0.1:9545'
}
},
contracts: [Forum],
events: {
Forum: ['UserSignedUp', 'UsernameUpdated', 'TopicCreated', 'PostCreated']
},
polls: {
accounts: 2000,
blocks: 2000
},
web3: {
fallback: {
type: 'ws',
url: 'ws://127.0.0.1:9545'
}
},
contracts: [Forum],
events: {
Forum: ['UserSignedUp', 'UsernameUpdated', 'TopicCreated', 'PostCreated']
},
polls: {
accounts: 2000,
blocks: 2000
}
};
export default drizzleOptions;
export default drizzleOptions;

24
app/src/config/ipfsOptions.js

@ -1,17 +1,19 @@
// OrbitDB uses Pubsub which is an experimental feature
// and need to be turned on manually.
const ipfsOptions = {
EXPERIMENTAL: {
pubsub: true
}, config: {
Addresses: {
Swarm: [
'/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)
'/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star'
]
}
EXPERIMENTAL: {
pubsub: true
},
config: {
Addresses: {
Swarm: [
'/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)
// (e.g. rendezvous --port=9090 --host=127.0.0.1)
'/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star'
]
}
}
};
export default ipfsOptions;
export default ipfsOptions;

193
app/src/containers/BoardContainer.js

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

47
app/src/containers/CoreLayoutContainer.js

@ -2,10 +2,8 @@ import React, { Component } from 'react';
import NavBarContainer from './NavBarContainer';
import RightSideBarContainer from './TransactionsMonitorContainer';
/*import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer';*/
// 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/sign-up-container.css';
@ -14,32 +12,33 @@ import '../assets/css/start-topic-container.css';
import '../assets/css/topic-container.css';
import '../assets/css/profile-container.css';
/* import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer'; */
class CoreLayout extends Component {
render() {
return (
<div className="App">
<NavBarContainer/>
{/*<div className="progress-bar-container"
render() {
return (
<div className="App">
<NavBarContainer />
{/* <div className="progress-bar-container"
style={{display: this.props.isProgressBarVisible ? "block" : "none"}}>
<div className="progress">
<div className="indeterminate"></div>
</div>
</div>*/}
<div className="page-container">
<aside className="left-side-panel">
</aside>
<div className="main-panel">
<div className="view-container">
{this.props.children}
</div>
</div>
<aside className="right-side-panel">
<RightSideBarContainer />
</aside>
</div>
</div> */}
<div className="page-container">
<aside className="left-side-panel" />
<div className="main-panel">
<div className="view-container">
{this.props.children}
</div>
);
}
</div>
<aside className="right-side-panel">
<RightSideBarContainer />
</aside>
</div>
</div>
);
}
}
export default CoreLayout
export default CoreLayout;

28
app/src/containers/HomeContainer.js

@ -4,22 +4,22 @@ import BoardContainer from './BoardContainer';
class HomeContainer extends Component {
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 && (
<Modal dimmer='blurring' open={this.state.open}>
<Modal.Header>Select a Photo</Modal.Header>
<Modal.Content image>
<Image wrapped size='medium' src='/assets/images/avatar/large/rachel.png' />
<Modal.Description>
<Header>Default Profile Image</Header>
<p>We've found the following gravatar image associated with your e-mail address.</p>
<p>Is it okay to use this photo?</p>
</Modal.Description>
</Modal.Content>
</Modal>);*/
/* var modal = this.props.user.hasSignedUp && (
<Modal dimmer='blurring' open={this.state.open}>
<Modal.Header>Select a Photo</Modal.Header>
<Modal.Content image>
<Image wrapped size='medium' src='/assets/images/avatar/large/rachel.png' />
<Modal.Description>
<Header>Default Profile Image</Header>
<p>We've found the following gravatar image associated with your e-mail address.</p>
<p>Is it okay to use this photo?</p>
</Modal.Description>
</Modal.Content>
</Modal>); */
return (<BoardContainer/>);
return (<BoardContainer />);
}
}

88
app/src/containers/NavBarContainer.js

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

323
app/src/containers/ProfileContainer.js

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

67
app/src/containers/SignUpContainer.js

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

241
app/src/containers/StartTopicContainer.js

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

291
app/src/containers/TopicContainer.js

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { push } from 'connected-react-router'
import { push } from 'connected-react-router';
import { connect } from 'react-redux';
import { drizzle } from '../index';
@ -10,157 +10,180 @@ import FloatingButton from '../components/FloatingButton';
import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js';
const contract = "Forum";
const getTopicMethod = "getTopic";
const contract = 'Forum';
const getTopicMethod = 'getTopic';
class TopicContainer extends Component {
constructor(props) {
super(props);
//Topic ID should be a positive integer
if (!/^[0-9]+$/.test(this.props.match.params.topicId)){
this.props.navigateTo('/404');
}
this.getBlockchainData = this.getBlockchainData.bind(this);
this.fetchTopicSubject = this.fetchTopicSubject.bind(this);
this.togglePostingState = this.togglePostingState.bind(this);
this.postCreated = this.postCreated.bind(this);
this.state = {
pageStatus: 'initialized',
topicID: parseInt(this.props.match.params.topicId),
topicSubject: null,
postFocus: this.props.match.params.postId && /^[0-9]+$/.test(this.props.match.params.postId)
? this.props.match.params.postId
: null,
fetchTopicSubjectStatus: 'pending',
posting: false
};
}
constructor(props) {
super(props);
getBlockchainData() {
if (this.state.pageStatus === 'initialized' &&
this.props.drizzleStatus['initialized']) {
this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(this.state.topicID);
this.setState({ pageStatus: 'loading' });
}
if (this.state.pageStatus === 'loading' &&
this.props.contracts[contract][getTopicMethod][this.dataKey]) {
this.setState({ pageStatus: 'loaded' });
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' &&
this.state.fetchTopicSubjectStatus === 'pending' &&
this.props.orbitDB.orbitdb !== null) {
this.fetchTopicSubject(this.props.contracts[contract][getTopicMethod][this.dataKey].value[0]);
this.setState({ fetchTopicSubjectStatus: 'fetching' });
}
// Topic ID should be a positive integer
if (!/^[0-9]+$/.test(this.props.match.params.topicId)) {
this.props.navigateTo('/404');
}
async fetchTopicSubject(orbitDBAddress) {
let orbitData;
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1] === this.props.user.address) {
orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID);
} else {
const fullAddress = "/orbitdb/" + orbitDBAddress + "/topics";
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
let localOrbitData = store.get(this.state.topicID);
if (localOrbitData) {
orbitData = localOrbitData;
} else {
// Wait until we have received something from the network
store.events.on('replicated', () => {
orbitData = store.get(this.state.topicID);
})
}
}
this.props.setNavBarTitle(orbitData['subject']);
this.getBlockchainData = this.getBlockchainData.bind(this);
this.fetchTopicSubject = this.fetchTopicSubject.bind(this);
this.togglePostingState = this.togglePostingState.bind(this);
this.postCreated = this.postCreated.bind(this);
this.state = {
pageStatus: 'initialized',
topicID: parseInt(this.props.match.params.topicId),
topicSubject: null,
postFocus: this.props.match.params.postId
&& /^[0-9]+$/.test(this.props.match.params.postId)
? this.props.match.params.postId
: null,
fetchTopicSubjectStatus: 'pending',
posting: false
};
}
getBlockchainData() {
if (this.state.pageStatus === 'initialized'
&& this.props.drizzleStatus.initialized) {
this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(
this.state.topicID,
);
this.setState({
pageStatus: 'loading'
});
}
if (this.state.pageStatus === 'loading'
&& this.props.contracts[contract][getTopicMethod][this.dataKey]) {
this.setState({
pageStatus: 'loaded'
});
if (this.props.orbitDB.orbitdb !== null) {
this.fetchTopicSubject(
this.props.contracts[contract][getTopicMethod][this.dataKey].value[0],
);
this.setState({
topicSubject: orbitData['subject'],
fetchTopicSubjectStatus: 'fetched'
fetchTopicSubjectStatus: 'fetching'
});
}
}
togglePostingState(event) {
if (event){
event.preventDefault();
}
this.setState(prevState => ({
posting: !prevState.posting
}));
if (this.state.pageStatus === 'loaded'
&& this.state.fetchTopicSubjectStatus === 'pending'
&& this.props.orbitDB.orbitdb !== null) {
this.fetchTopicSubject(
this.props.contracts[contract][getTopicMethod][this.dataKey].value[0],
);
this.setState({
fetchTopicSubjectStatus: 'fetching'
});
}
postCreated(){
this.setState(prevState => ({
posting: false
}));
}
async fetchTopicSubject(orbitDBAddress) {
let orbitData;
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1]
=== this.props.user.address) {
orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID);
} else {
const fullAddress = `/orbitdb/${orbitDBAddress}/topics`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
const localOrbitData = store.get(this.state.topicID);
if (localOrbitData) {
orbitData = localOrbitData;
} else {
// Wait until we have received something from the network
store.events.on('replicated', () => {
orbitData = store.get(this.state.topicID);
});
}
}
render() {
var topicContents;
if (this.state.pageStatus === 'loaded') {
topicContents = (
(<div>
<PostList postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]}
focusOnPost={this.state.postFocus ? this.state.postFocus : null}/>
{this.state.posting &&
<NewPost topicID={this.state.topicID}
subject={this.state.topicSubject}
postIndex={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4].length}
onCancelClick={() => {this.togglePostingState()}}
onPostCreated={() => {this.postCreated()}}
/>
}
<div className="posts-list-spacer"></div>
{this.props.user.hasSignedUp && !this.state.posting &&
<FloatingButton onClick={this.togglePostingState}/>
}
</div>)
)
}
return (
<div className="fill">
{topicContents}
{!this.state.posting &&
<div className="bottom-overlay-pad"></div>
}
</div>
);
}
this.props.setNavBarTitle(orbitData.subject);
this.setState({
topicSubject: orbitData.subject,
fetchTopicSubjectStatus: 'fetched'
});
}
componentDidMount() {
this.getBlockchainData();
togglePostingState(event) {
if (event) {
event.preventDefault();
}
componentDidUpdate() {
this.getBlockchainData();
this.setState(prevState => ({
posting: !prevState.posting
}));
}
postCreated() {
this.setState(prevState => ({
posting: false
}));
}
render() {
let topicContents;
if (this.state.pageStatus === 'loaded') {
topicContents = (
(
<div>
<PostList
postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]}
focusOnPost={this.state.postFocus
? this.state.postFocus
: null}
/>
{this.state.posting
&& (
<NewPost
topicID={this.state.topicID}
subject={this.state.topicSubject}
postIndex={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4].length}
onCancelClick={() => { this.togglePostingState(); }}
onPostCreated={() => { this.postCreated(); }}
/>
)
}
<div className="posts-list-spacer" />
{this.props.user.hasSignedUp && !this.state.posting
&& <FloatingButton onClick={this.togglePostingState} />
}
</div>
)
);
}
componentWillUnmount() {
this.props.setNavBarTitle('');
}
return (
<div className="fill">
{topicContents}
{!this.state.posting
&& <div className="bottom-overlay-pad" />
}
</div>
);
}
componentDidMount() {
this.getBlockchainData();
}
componentDidUpdate() {
this.getBlockchainData();
}
componentWillUnmount() {
this.props.setNavBarTitle('');
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location),
setNavBarTitle: (navBarTitle) => setNavBarTitle(navBarTitle)
navigateTo: location => push(location),
setNavBarTitle: navBarTitle => setNavBarTitle(navBarTitle)
}, dispatch);
const mapStateToProps = state => {
return {
user: state.user,
contracts: state.contracts,
drizzleStatus: state.drizzleStatus,
orbitDB: state.orbit
}
};
const mapStateToProps = state => ({
user: state.user,
contracts: state.contracts,
drizzleStatus: state.drizzleStatus,
orbitDB: state.orbit
});
export default connect(mapStateToProps, mapDispatchToProps)(TopicContainer);
export default connect(mapStateToProps, mapDispatchToProps)(TopicContainer);

221
app/src/containers/TransactionsMonitorContainer.js

@ -1,130 +1,139 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'
import { withRouter } from 'react-router-dom';
import { Message } from 'semantic-ui-react';
class RightSideBar extends Component {
constructor(props, context) {
super(props);
constructor(props, context) {
super(props);
this.handleMessageClick = this.handleMessageClick.bind(this);
this.handleMessageDismiss = this.handleMessageDismiss.bind(this);
this.handleMessageClick = this.handleMessageClick.bind(this);
this.handleMessageDismiss = this.handleMessageDismiss.bind(this);
this.state = {
isTransactionMessageDismissed: []
this.state = {
isTransactionMessageDismissed: []
};
}
handleMessageClick(index) {
const transactionHash = this.props.transactionStack[index];
if (this.props.transactions[transactionHash]) {
if (this.props.transactions[transactionHash].status === 'error') {
this.handleMessageDismiss(null, index);
} else if (this.props.transactions[transactionHash].receipt
&& this.props.transactions[transactionHash].receipt.events) {
switch (Object.keys(
this.props.transactions[transactionHash].receipt.events,
)[0]) {
case 'UserSignedUp':
this.props.history.push('/profile');
this.handleMessageDismiss(null, index);
break;
case 'UsernameUpdated':
this.props.history.push('/profile');
this.handleMessageDismiss(null, index);
break;
case 'TopicCreated':
this.props.history.push(`/topic/${
this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID}`);
this.handleMessageDismiss(null, index);
break;
case 'PostCreated':
this.props.history.push(`/topic/${
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID
}/${
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID}`);
this.handleMessageDismiss(null, index);
break;
default:
this.handleMessageDismiss(null, index);
break;
}
}
}
}
handleMessageClick(index) {
let transactionHash = this.props.transactionStack[index];
if (this.props.transactions[transactionHash]) {
if (this.props.transactions[transactionHash].status === 'error') {
this.handleMessageDismiss(null, index);
} else {
if (this.props.transactions[transactionHash].receipt &&
this.props.transactions[transactionHash].receipt.events) {
switch (Object.keys(this.props.transactions[transactionHash].receipt.events)[0]){
case 'UserSignedUp':
this.props.history.push("/profile");
this.handleMessageDismiss(null, index);
break;
case 'UsernameUpdated':
this.props.history.push("/profile");
this.handleMessageDismiss(null, index);
break;
case 'TopicCreated':
this.props.history.push("/topic/" +
this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID
);
this.handleMessageDismiss(null, index);
break;
case 'PostCreated':
this.props.history.push("/topic/" +
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID +
"/" +
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID
);
this.handleMessageDismiss(null, index);
break;
default:
this.handleMessageDismiss(null, index);
break;
}
}
}
}
handleMessageDismiss(event, messageIndex) {
if (event !== null) {
event.stopPropagation();
}
handleMessageDismiss(event, messageIndex) {
if (event !== null) {
event.stopPropagation();
}
const isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice();
isTransactionMessageDismissedShallowCopy[messageIndex] = true;
this.setState({
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy
});
}
let isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice();
isTransactionMessageDismissedShallowCopy[messageIndex] = true;
this.setState({
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy
});
render() {
if (this.props.transactionStack.length === 0) {
return null;
}
render() {
if (this.props.transactionStack.length === 0){
return null;
const transactionMessages = this.props.transactionStack.map(
(transaction, index) => {
if (this.state.isTransactionMessageDismissed[index]) {
return null;
}
let transactionMessages = this.props.transactionStack.map((transaction, index) => {
if (this.state.isTransactionMessageDismissed[index]){
return null;
}
let color = 'black';
let message = [];
message.push("New transaction has been queued and is waiting your confirmation.");
if (this.props.transactions[transaction]) {
message.push(<br key="confirmed"/>);
message.push("- transaction confirmed");
}
if (this.props.transactions[transaction] &&
this.props.transactions[transaction].status === 'success') {
/* Transaction completed successfully */
message.push(<br key="mined"/>);
message.push("- transaction mined");
color = 'green';
message.push(<br key="success"/>);
message.push("- transaction completed successfully");
} else if (this.props.transactions[transaction] &&
this.props.transactions[transaction].status === "error"){
/* Transaction failed to complete */
message.push(<br key="mined"/>);
message.push("- transaction mined");
color = 'red';
message.push(<br key="fail"/>);
message.push("Transaction failed to complete!");
}
let color = 'black';
const message = [];
message.push(
'New transaction has been queued and is waiting your confirmation.',
);
if (this.props.transactions[transaction]) {
message.push(<br key="confirmed" />);
message.push('- transaction confirmed');
}
if (this.props.transactions[transaction]
&& this.props.transactions[transaction].status === 'success') {
/* Transaction completed successfully */
message.push(<br key="mined" />);
message.push('- transaction mined');
color = 'green';
message.push(<br key="success" />);
message.push('- transaction completed successfully');
} else if (this.props.transactions[transaction]
&& this.props.transactions[transaction].status === 'error') {
/* Transaction failed to complete */
message.push(<br key="mined" />);
message.push('- transaction mined');
color = 'red';
message.push(<br key="fail" />);
message.push('Transaction failed to complete!');
}
return (
<div className="sidebar-message" key={index}
onClick={() => {this.handleMessageClick(index)}} >
<Message color={color}
onDismiss={(e) => {this.handleMessageDismiss(e, index)}}>
{message}
</Message>
</div>
);
});
return (
<div
className="sidebar-message"
key={index}
onClick={() => { this.handleMessageClick(index); }}
>
<Message
color={color}
onDismiss={(e) => {
this.handleMessageDismiss(e, index);
}}
>
{message}
</Message>
</div>
);
},
);
return (transactionMessages);
}
return (transactionMessages);
}
}
const mapStateToProps = state => {
return {
transactions: state.transactions,
transactionStack: state.transactionStack
}
};
const mapStateToProps = state => ({
transactions: state.transactions,
transactionStack: state.transactionStack
});
const RightSideBarContainer = withRouter(connect(mapStateToProps)(RightSideBar));
const RightSideBarContainer = withRouter(
connect(mapStateToProps)(RightSideBar),
);
export default RightSideBarContainer;
export default RightSideBarContainer;

329
app/src/containers/UsernameFormContainer.js

@ -1,175 +1,196 @@
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 { createDatabases } from '../utils/orbitUtils';
import { updateUsername } from '../redux/actions/transactionsActions';
const contract = "Forum";
const checkUsernameTakenMethod = "isUserNameTaken";
const signUpMethod = "signUp";
const contract = 'Forum';
const checkUsernameTakenMethod = 'isUserNameTaken';
const signUpMethod = 'signUp';
class UsernameFormContainer extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.completeAction = this.completeAction.bind(this);
this.checkedUsernames = [];
this.state = {
usernameInput: '',
error: false,
errorHeader: "",
errorMessage: "",
signingUp: false
};
}
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.completeAction = this.completeAction.bind(this);
this.checkedUsernames = [];
this.state = {
usernameInput: '',
error: false,
errorHeader: '',
errorMessage: '',
signingUp: false
};
}
handleInputChange(e, { name, value }) {
this.setState({
[name]: value,
error: false
});
if (value !== '') {
if (this.checkedUsernames.length > 0) {
if (this.checkedUsernames.some(e => e.usernameChecked === value)) {
return;
}
}
handleInputChange(e, { name, value }) {
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(
value,
);
}
}
handleSubmit() {
if (this.state.usernameInput === '') {
this.setState({
error: true,
errorHeader: 'Data Incomplete',
errorMessage: 'You need to provide a username'
});
} else if (!this.state.error) {
// Makes sure current input username has been checked for availability
if (this.checkedUsernames.some(
e => e.usernameChecked === this.state.usernameInput,
)) {
this.completeAction();
}
}
}
async completeAction() {
if (this.props.user.hasSignedUp) {
this.props.dispatch(updateUsername(...[this.state.usernameInput], null));
} else {
this.setState({
signingUp: true
});
const orbitdbInfo = await createDatabases();
this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend(
...[
this.state.usernameInput,
orbitdbInfo.identityId,
orbitdbInfo.identityPublicKey,
orbitdbInfo.identityPrivateKey,
orbitdbInfo.orbitId,
orbitdbInfo.orbitPublicKey,
orbitdbInfo.orbitPrivateKey,
orbitdbInfo.topicsDB,
orbitdbInfo.postsDB
], {
from: this.props.account
},
);
}
this.setState({
usernameInput: ''
});
}
componentDidUpdate() {
if (this.state.signingUp) {
const txHash = this.props.transactionStack[this.stackId];
if (txHash
&& this.props.transactions[txHash]
&& this.props.transactions[txHash].status === 'error') {
this.setState({
[name]: value,
error: false
signingUp: false
});
if (value !== '') {
if (this.checkedUsernames.length > 0) {
if (this.checkedUsernames.some(e => e.usernameChecked === value)){
return;
}
}
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(value);
}
}
handleSubmit() {
if (this.state.usernameInput === ''){
}
} else {
const temp = Object.values(
this.props.contracts[contract][checkUsernameTakenMethod],
);
this.checkedUsernames = temp.map(checked => ({
usernameChecked: checked.args[0],
isTaken: checked.value
}));
if (this.checkedUsernames.length > 0) {
this.checkedUsernames.forEach((checked) => {
if (checked.usernameChecked === this.state.usernameInput
&& checked.isTaken && !this.state.error) {
this.setState({
error: true,
errorHeader: "Data Incomplete",
errorMessage: "You need to provide a username"
error: true,
errorHeader: 'Data disapproved',
errorMessage: 'This username is already taken'
});
} else if (!this.state.error) {
// Makes sure current input username has been checked for availability
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){
this.completeAction();
}
}
}
async completeAction() {
if(this.props.user.hasSignedUp){
this.props.dispatch(updateUsername(...[this.state.usernameInput], null));
} else {
this.setState({ signingUp: true });
const orbitdbInfo = await createDatabases();
this.stackId = drizzle.contracts[contract].methods[signUpMethod]
.cacheSend(...[this.state.usernameInput,
orbitdbInfo.identityId,
orbitdbInfo.identityPublicKey,
orbitdbInfo.identityPrivateKey,
orbitdbInfo.orbitId,
orbitdbInfo.orbitPublicKey,
orbitdbInfo.orbitPrivateKey,
orbitdbInfo.topicsDB,
orbitdbInfo.postsDB
], { from: this.props.account});
}
this.setState({ usernameInput: '' });
}
});
}
}
componentDidUpdate() {
if (this.state.signingUp) {
const txHash = this.props.transactionStack[this.stackId];
if (txHash &&
this.props.transactions[txHash] &&
this.props.transactions[txHash].status === "error") {
this.setState({signingUp: false});
}
} else {
const temp = Object.values(this.props.contracts[contract][checkUsernameTakenMethod]);
this.checkedUsernames = temp.map(checked => {return {
usernameChecked: checked.args[0],
isTaken: checked.value
}});
if (this.checkedUsernames.length > 0){
this.checkedUsernames.forEach( checked => {
if (checked.usernameChecked === this.state.usernameInput &&
checked.isTaken && !this.state.error) {
this.setState({
error: true,
errorHeader: "Data disapproved",
errorMessage: "This username is already taken"
});
}
})
}
}
}
render() {
const { hasSignedUp } = this.props.user;
if (hasSignedUp !== null) {
const buttonText = hasSignedUp ? 'Update' : 'Sign Up';
const placeholderText = hasSignedUp
? this.props.user.username
: 'Username';
const withError = this.state.error && {
error: true
};
/* var disableSubmit = true;
if (this.checkedUsernames.length > 0) {
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){
disableSubmit = false;
}
} else {
disableSubmit = false;
}
disableSubmit = (disableSubmit || this.state.error) && {loading: true}; */
return (
<div>
<Form onSubmit={this.handleSubmit} {...withError}>
<Form.Field required>
<label>Username</label>
<Form.Input
placeholder={placeholderText}
name="usernameInput"
value={this.state.usernameInput}
onChange={this.handleInputChange}
/>
</Form.Field>
<Message
error
header={this.state.errorHeader}
content={this.state.errorMessage}
/>
<Button type="submit">{buttonText}</Button>
</Form>
<Dimmer active={this.state.signingUp} page>
<Header as="h2" inverted>
<Loader size="large">
Magic elves are processing your noble
request.
</Loader>
</Header>
</Dimmer>
</div>
);
}
render() {
const hasSignedUp = this.props.user.hasSignedUp;
if(hasSignedUp !== null) {
const buttonText = hasSignedUp ? "Update" : "Sign Up";
const placeholderText = hasSignedUp ? this.props.user.username : "Username";
const withError = this.state.error && {error: true};
/*var disableSubmit = true;
if (this.checkedUsernames.length > 0) {
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){
disableSubmit = false;
}
} else {
disableSubmit = false;
}
disableSubmit = (disableSubmit || this.state.error) && {loading: true};*/
return(
<div>
<Form onSubmit={this.handleSubmit} {...withError}>
<Form.Field required>
<label>Username</label>
<Form.Input
placeholder={placeholderText}
name='usernameInput'
value={this.state.usernameInput}
onChange={this.handleInputChange}
/>
</Form.Field>
<Message
error
header={this.state.errorHeader}
content={this.state.errorMessage}
/>
<Button type='submit'>{buttonText}</Button>
</Form>
<Dimmer active={this.state.signingUp} page>
<Header as='h2' inverted>
<Loader size='large'>Magic elves are processing your noble request.</Loader>
</Header>
</Dimmer>
</div>
);
}
return(null);
}
return (null);
}
}
const mapStateToProps = state => {
return {
account: state.accounts[0],
contracts: state.contracts,
transactions: state.transactions,
transactionStack: state.transactionStack,
user: state.user
}
};
const mapStateToProps = state => ({
account: state.accounts[0],
contracts: state.contracts,
transactions: state.transactions,
transactionStack: state.transactionStack,
user: state.user
});
export default connect(mapStateToProps)(UsernameFormContainer);

20
app/src/helpers/EpochTimeConverter.js

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

22
app/src/index.js

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

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

@ -3,14 +3,18 @@ const DATABASES_CREATED = 'DATABASES_CREATED';
const DATABASES_LOADED = 'DATABASES_LOADED';
const DATABASES_NOT_READY = 'DATABASES_NOT_READY';
function updateDatabases(type, orbitdb, topicsDB, postsDB){
return {
type,
orbitdb,
topicsDB,
postsDB,
id: orbitdb.id
};
function updateDatabases(type, orbitdb, topicsDB, postsDB) {
return {
type,
orbitdb,
topicsDB,
postsDB,
id: orbitdb.id
};
}
export { DATABASES_CREATED, DATABASES_LOADED, DATABASES_NOT_READY, IPFS_INITIALIZED, updateDatabases }
export { DATABASES_CREATED,
DATABASES_LOADED,
DATABASES_NOT_READY,
IPFS_INITIALIZED,
updateDatabases };

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

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

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

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

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

@ -1,53 +1,56 @@
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 = {
ipfs: null,
ipfsInitialized: false,
ready: false,
orbitdb: null,
topicsDB: null,
postsDB: null,
id: null
ipfs: null,
ipfsInitialized: false,
ready: false,
orbitdb: null,
topicsDB: null,
postsDB: null,
id: null
};
const orbitReducer = (state = initialState, action) => {
switch (action.type) {
case IPFS_INITIALIZED:
return {
...state,
ipfs: action.ipfs,
ipfsInitialized: true
};
case DATABASES_CREATED:
return {
...state,
ready: true,
orbitdb: action.orbitdb,
topicsDB: action.topicsDB,
postsDB: action.postsDB,
id: action.id
};
case DATABASES_LOADED:
return {
...state,
ready: true,
orbitdb: action.orbitdb,
topicsDB: action.topicsDB,
postsDB: action.postsDB,
id: action.id
};
case DATABASES_NOT_READY:
return {
...state,
ready: false,
orbitdb: null,
topicsDB: null,
postsDB: null,
id: null
};
default:
return state
}
switch (action.type) {
case IPFS_INITIALIZED:
return {
...state,
ipfs: action.ipfs,
ipfsInitialized: true
};
case DATABASES_CREATED:
return {
...state,
ready: true,
orbitdb: action.orbitdb,
topicsDB: action.topicsDB,
postsDB: action.postsDB,
id: action.id
};
case DATABASES_LOADED:
return {
...state,
ready: true,
orbitdb: action.orbitdb,
topicsDB: action.topicsDB,
postsDB: action.postsDB,
id: action.id
};
case DATABASES_NOT_READY:
return {
...state,
ready: false,
orbitdb: null,
topicsDB: null,
postsDB: null,
id: null
};
default:
return state;
}
};
export default orbitReducer;

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

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

18
app/src/redux/reducers/userInterfaceReducer.js

@ -1,18 +1,18 @@
import { SET_NAVBAR_TITLE } from '../actions/userInterfaceActions';
const initialState = {
navBarTitle: ''
navBarTitle: ''
};
const userInterfaceReducer = (state = initialState, action) => {
switch (action.type) {
case SET_NAVBAR_TITLE:
return {
navBarTitle: action.title
};
default:
return state;
}
switch (action.type) {
case SET_NAVBAR_TITLE:
return {
navBarTitle: action.title
};
default:
return state;
}
};
export default userInterfaceReducer;

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

@ -1,27 +1,27 @@
const initialState = {
username: "",
address: "0x0",
avatarUrl: "",
hasSignedUp: null
username: '',
address: '0x0',
avatarUrl: '',
hasSignedUp: null
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case 'USER_DATA_UPDATED_(AUTHENTICATED)':
return {
username: action.username,
address: action.address,
hasSignedUp: true
};
case 'USER_DATA_UPDATED_(GUEST)':
return {
username: "",
address: action.address,
hasSignedUp: false
};
default:
return state
}
switch (action.type) {
case 'USER_DATA_UPDATED_(AUTHENTICATED)':
return {
username: action.username,
address: action.address,
hasSignedUp: true
};
case 'USER_DATA_UPDATED_(GUEST)':
return {
username: '',
address: action.address,
hasSignedUp: false
};
default:
return state;
}
};
export default userReducer;
export default userReducer;

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

@ -1,38 +1,37 @@
import { getContractInstance, getWeb3 } from "../../utils/drizzleUtils";
import { call, put, takeLatest, select } from 'redux-saga/effects'
import { call, put, select, takeLatest } from 'redux-saga/effects';
import { getContractInstance, getWeb3 } from '../../utils/drizzleUtils';
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;
let initFlag, web3, contract;
const accounts = state => state.accounts;
let initFlag; let web3; let
contract;
function* init() {
if(!initFlag) {
web3 = yield call(getWeb3);
contract = yield call(getContractInstance,{
web3: web3,
artifact: Forum
});
initFlag=true;
yield put({type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]});
}
else
console.warn("Attempted to reinitialize drizzleUtilsSaga!");
if (!initFlag) {
web3 = yield call(getWeb3);
contract = yield call(getContractInstance, {
web3, artifact: Forum
});
initFlag = true;
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
// from state), consider getting it from @drizzle-utils/get-accounts instead
// with (yield call(getAccounts, {web3}))[0];
function* getCurrentAccount(){
return (yield select(accounts))[0];
function* getCurrentAccount() {
return (yield select(accounts))[0];
}
function* drizzleUtilsSaga() {
yield takeLatest("DRIZZLE_INITIALIZED", init);
yield takeLatest('DRIZZLE_INITIALIZED', init);
}
export { web3, contract, getCurrentAccount }
export { web3, contract, getCurrentAccount };
export default drizzleUtilsSaga;

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

@ -1,46 +1,62 @@
import {all, call, put, take, takeLatest} from 'redux-saga/effects'
import { contract, getCurrentAccount} from './drizzleUtilsSaga';
import { loadDatabases } from '../../utils/orbitUtils'
import { all, call, put, take, takeLatest } from 'redux-saga/effects';
import { contract, getCurrentAccount } from './drizzleUtilsSaga';
import { loadDatabases } from '../../utils/orbitUtils';
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;
function* getOrbitDBInfo() {
yield put({type: 'ORRBIT_GETTING_INFO', ...[]});
const account = yield call(getCurrentAccount);
if(account!==latestAccount) {
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]);
try {
const callResult = yield call(txObj1.call, {address:account});
if(callResult) {
// console.log("Deleting local storage..");
// localStorage.clear();
const txObj2 = yield call(contract.methods["getOrbitIdentityInfo"], ...[account]);
const orbitIdentityInfo = yield call(txObj2.call, {address: account});
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, ...[]});
yield put({
type: 'ORRBIT_GETTING_INFO', ...[]
});
const account = yield call(getCurrentAccount);
if (account !== latestAccount) {
const txObj1 = yield call(contract.methods.hasUserSignedUp,
...[account]);
try {
const callResult = yield call(txObj1.call, {
address: account
});
if (callResult) {
// console.log("Deleting local storage..");
// localStorage.clear();
const txObj2 = yield call(contract.methods.getOrbitIdentityInfo,
...[account]);
const orbitIdentityInfo = yield call(txObj2.call, {
address: account
});
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, ...[]
});
}
latestAccount=account;
}
catch (error) {
console.error(error);
yield put({type: 'ORBIT_SAGA_ERROR', ...[]});
}
latestAccount = account;
} catch (error) {
console.error(error);
yield put({
type: 'ORBIT_SAGA_ERROR', ...[]
});
}
}
}
function* orbitSaga() {
yield all([
take(DRIZZLE_UTILS_SAGA_INITIALIZED),
take(IPFS_INITIALIZED)
]);
yield takeLatest("ACCOUNT_CHANGED", getOrbitDBInfo);
yield all([
take(DRIZZLE_UTILS_SAGA_INITIALIZED),
take(IPFS_INITIALIZED)
]);
yield takeLatest('ACCOUNT_CHANGED', getOrbitDBInfo);
}
export default orbitSaga;

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

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

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

84
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 { DRIZZLE_UTILS_SAGA_INITIALIZED } from "../actions/drizzleUtilsActions";
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions';
let account;
function* updateUserData() {
const currentAccount = yield call(getCurrentAccount);
if(currentAccount!==account) {
account = currentAccount;
yield put({type: 'ACCOUNT_CHANGED', ...[]});
}
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]);
try {
const userState = yield call(getUserState);
const callResult = yield call(txObj1.call, {address:account});
if(callResult) {
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 = {
address: account,
username: username
};
yield put({type: 'USER_DATA_UPDATED_(AUTHENTICATED)', ...dispatchArgs});
}
}
else{
if(account!==userState.address){
const dispatchArgs = {
address: account
};
yield put({type: 'USER_DATA_UPDATED_(GUEST)', ...dispatchArgs});
}
}
}
catch (error) {
console.error(error);
yield put({type: 'USER_FETCHING_ERROR', ...[]})
const currentAccount = yield call(getCurrentAccount);
if (currentAccount !== account) {
account = currentAccount;
yield put({
type: 'ACCOUNT_CHANGED', ...[]
});
}
const txObj1 = yield call(contract.methods.hasUserSignedUp, ...[account]);
try {
const userState = yield call(getUserState);
const callResult = yield call(txObj1.call, {
address: account
});
if (callResult) {
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 = {
address: account,
username
};
yield put({
type: 'USER_DATA_UPDATED_(AUTHENTICATED)', ...dispatchArgs
});
}
} else if (account !== userState.address) {
const dispatchArgs = {
address: account
};
yield put({
type: 'USER_DATA_UPDATED_(GUEST)', ...dispatchArgs
});
}
} catch (error) {
console.error(error);
yield put({
type: 'USER_FETCHING_ERROR', ...[]
});
}
}
function* getUserState(){
return yield select((state) => state.user);
function* getUserState() {
return yield select(state => state.user);
}
function* userSaga() {
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED);
yield takeEvery("ACCOUNTS_FETCHED", updateUserData);
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED);
yield takeEvery('ACCOUNTS_FETCHED', updateUserData);
}
export default userSaga;

25
app/src/redux/store.js

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

43
app/src/router/PrivateRoute.js

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

49
app/src/router/routes.js

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

140
app/src/utils/drizzleUtils.js

@ -1,88 +1,86 @@
// See also: https://github.com/trufflesuite/drizzle-utils
const Web3 = require("web3");
const Web3 = require('web3');
const resolveWeb3 = (resolve, options, isBrowser) => {
let provider;
let provider;
if (options.customProvider) {
// use custom provider from options object
provider = options.customProvider;
} else if (isBrowser && window.ethereum) {
// use `ethereum` object injected by MetaMask
provider = window.ethereum;
} else if (isBrowser && typeof window.web3 !== "undefined") {
// use injected web3 object by legacy dapp browsers
provider = window.web3.currentProvider;
} else if (options.fallbackProvider) {
// use fallback provider from options object
provider = options.fallbackProvider;
} else {
// connect to development blockchain from `truffle develop`
provider = new Web3.providers.HttpProvider("http://127.0.0.1:9545");
}
if (options.customProvider) {
// use custom provider from options object
provider = options.customProvider;
} else if (isBrowser && window.ethereum) {
// use `ethereum` object injected by MetaMask
provider = window.ethereum;
} else if (isBrowser && typeof window.web3 !== 'undefined') {
// use injected web3 object by legacy dapp browsers
provider = window.web3.currentProvider;
} else if (options.fallbackProvider) {
// use fallback provider from options object
provider = options.fallbackProvider;
} else {
// connect to development blockchain from `truffle develop`
provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545');
}
const web3 = new Web3(provider);
resolve(web3);
const web3 = new Web3(provider);
resolve(web3);
};
const getWeb3 = (options = {}) =>
new Promise(resolve => {
// handle server-side and React Native environments
const isReactNative =
typeof navigator !== "undefined" && navigator.product === "ReactNative";
const isNode = typeof window === "undefined";
if (isNode || isReactNative) {
return resolveWeb3(resolve, options, false);
}
const getWeb3 = (options = {
}) => new Promise((resolve) => {
// handle server-side and React Native environments
const isReactNative = typeof navigator !== 'undefined' && navigator.product
=== 'ReactNative';
const isNode = typeof window === 'undefined';
if (isNode || isReactNative) {
return resolveWeb3(resolve, options, false);
}
// if page is ready, resolve for web3 immediately
if (document.readyState === `complete`) {
return resolveWeb3(resolve, options, true);
}
// if page is ready, resolve for web3 immediately
if (document.readyState === 'complete') {
return resolveWeb3(resolve, options, true);
}
// otherwise, resolve for web3 when page is done loading
return window.addEventListener("load", () =>
resolveWeb3(resolve, options, true),
);
});
// otherwise, resolve for web3 when page is done loading
return window.addEventListener('load', () => resolveWeb3(resolve, options, true));
});
const getContractInstance = (options = {}) =>
new Promise(async (resolve, reject) => {
if (!options.web3) {
return reject(new Error("The options object with web3 is required."));
}
const getContractInstance = (options = {
}) => new Promise(async (resolve, reject) => {
if (!options.web3) {
return reject(new Error('The options object with web3 is required.'));
}
const { web3 } = options;
const { web3 } = options;
let instance;
try {
if (options.artifact) {
// if artifact exists, attempt to get network ID and the deployed address
const { artifact } = options;
const networkId = await web3.eth.net.getId(); // web3 v1.0.0-beta.47 breaks here
const deployedNetwork = artifact.networks[networkId];
let instance;
try {
if (options.artifact) {
// if artifact exists, attempt to get network ID and the deployed address
const { artifact } = options;
const networkId = await web3.eth.net.getId(); // web3 v1.0.0-beta.47 breaks here
const deployedNetwork = artifact.networks[networkId];
// if no deployed address is found, instantiate without the address
const address = deployedNetwork && deployedNetwork.address;
// if no deployed address is found, instantiate without the address
const address = deployedNetwork && deployedNetwork.address;
instance = new web3.eth.Contract(artifact.abi, address);
} else if (options.abi) {
// otherwise, use passed-in ABI and deployed address (optional)
const { abi, address } = options;
instance = new web3.eth.Contract(artifact.abi, address);
} else if (options.abi) {
// otherwise, use passed-in ABI and deployed address (optional)
const { abi, address } = options;
instance = new web3.eth.Contract(abi, address);
} else {
return reject(
new Error(
"You must pass in a contract artifact or the ABI of a deployed contract.",
),
);
}
instance = new web3.eth.Contract(abi, address);
} else {
return reject(
new Error(
'You must pass in a contract artifact or the ABI of a deployed contract.',
),
);
}
return resolve(instance);
} catch (err) {
return reject(err);
}
});
return resolve(instance);
} catch (err) {
return reject(err);
}
});
export { getWeb3, getContractInstance };
export { getWeb3, getContractInstance };

92
app/src/utils/orbitUtils.js

@ -1,69 +1,77 @@
import OrbitDB from 'orbit-db';
import Keystore from 'orbit-db-keystore';
import path from 'path';
import IPFS from 'ipfs';
import store from '../redux/store';
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(){
const ipfs = new IPFS(ipfsOptions);
ipfs.on('ready', async () => {
store.dispatch({type: IPFS_INITIALIZED, ipfs});
console.log("IPFS initialized.");
function initIPFS() {
const ipfs = new IPFS(ipfsOptions);
ipfs.on('ready', async () => {
store.dispatch({
type: IPFS_INITIALIZED, ipfs
});
console.log('IPFS initialized.');
});
}
async function createDatabases() {
console.log("Creating databases...");
const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs);
const topicsDB = await orbitdb.keyvalue('topics');
const postsDB = await orbitdb.keyvalue('posts');
store.dispatch(updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB));
console.log('Creating databases...');
const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs);
const topicsDB = await orbitdb.keyvalue('topics');
const postsDB = await orbitdb.keyvalue('posts');
store.dispatch(
updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB),
);
const orbitKey = orbitdb.keystore.getKey(orbitdb.id);
const orbitKey = orbitdb.keystore.getKey(orbitdb.id);
return {
identityId: "Tempus",
identityPublicKey: "edax",
identityPrivateKey: "rerum",
orbitId: orbitdb.id,
orbitPublicKey: orbitKey.getPublic('hex'),
orbitPrivateKey: orbitKey.getPrivate('hex'),
topicsDB: topicsDB.address.root,
postsDB: postsDB.address.root
};
return {
identityId: 'Tempus',
identityPublicKey: 'edax',
identityPrivateKey: 'rerum',
orbitId: orbitdb.id,
orbitPublicKey: orbitKey.getPublic('hex'),
orbitPrivateKey: orbitKey.getPrivate('hex'),
topicsDB: topicsDB.address.root,
postsDB: postsDB.address.root
};
}
async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
orbitId, orbitPublicKey, orbitPrivateKey, topicsDBId, postsDBId) {
console.log("Loading databases...");
let directory = "./orbitdb";
let keystore = Keystore.create(path.join(directory, orbitId, '/keystore'));
orbitId, orbitPublicKey, orbitPrivateKey,
topicsDBId, postsDBId) {
console.log('Loading databases...');
const directory = './orbitdb';
const keystore = Keystore.create(path.join(directory, orbitId, '/keystore'));
keystore._storage.setItem(orbitId, JSON.stringify({
publicKey: orbitPublicKey,
privateKey: orbitPrivateKey
}));
keystore._storage.setItem(orbitId, JSON.stringify({
publicKey: orbitPublicKey,
privateKey: orbitPrivateKey
}));
const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs, directory, { peerId:orbitId, keystore:keystore});
const topicsDB = await orbitdb.keyvalue('/orbitdb/' + topicsDBId +'/topics');
const postsDB = await orbitdb.keyvalue('/orbitdb/' + postsDBId +'/posts');
const ipfs = getIPFS();
const orbitdb = await new OrbitDB(ipfs, directory,
{
peerId: orbitId, keystore
});
const topicsDB = await orbitdb.keyvalue(`/orbitdb/${topicsDBId}/topics`);
const postsDB = await orbitdb.keyvalue(`/orbitdb/${postsDBId}/posts`);
await topicsDB.load();
await postsDB.load();
await topicsDB.load();
await postsDB.load();
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB));
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB));
}
function getIPFS(){
return store.getState().orbit.ipfs;
function getIPFS() {
return store.getState().orbit.ipfs;
}
async function orbitSagaPut(db, key, value) {
db.put(key, value);
db.put(key, value);
}
export { initIPFS, createDatabases, loadDatabases, orbitSagaPut };

129
app/src/utils/serviceWorker.js

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

7
package.json

@ -13,5 +13,12 @@
},
"dependencies": {
"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"
}
}

40
truffle-config.js

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

Loading…
Cancel
Save