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. 11336
      app/src/assets/fonts/fontawesome-free-5.7.2/all.js
  7. 10
      app/src/components/FloatingButton.js
  8. 13
      app/src/components/LoadingSpinner.js
  9. 125
      app/src/components/NewPost.js
  10. 23
      app/src/components/NewTopicPreview.js
  11. 9
      app/src/components/NotFound.js
  12. 197
      app/src/components/Post.js
  13. 72
      app/src/components/PostList.js
  14. 62
      app/src/components/ProfileInformation.js
  15. 111
      app/src/components/Topic.js
  16. 38
      app/src/components/TopicList.js
  17. 4
      app/src/config/drizzleOptions.js
  18. 4
      app/src/config/ipfsOptions.js
  19. 73
      app/src/containers/BoardContainer.js
  20. 9
      app/src/containers/CoreLayoutContainer.js
  21. 42
      app/src/containers/NavBarContainer.js
  22. 109
      app/src/containers/ProfileContainer.js
  23. 31
      app/src/containers/SignUpContainer.js
  24. 89
      app/src/containers/StartTopicContainer.js
  25. 113
      app/src/containers/TopicContainer.js
  26. 91
      app/src/containers/TransactionsMonitorContainer.js
  27. 103
      app/src/containers/UsernameFormContainer.js
  28. 16
      app/src/helpers/EpochTimeConverter.js
  29. 10
      app/src/index.js
  30. 6
      app/src/redux/actions/orbitActions.js
  31. 6
      app/src/redux/actions/transactionsActions.js
  32. 7
      app/src/redux/reducers/orbitReducer.js
  33. 6
      app/src/redux/reducers/rootReducer.js
  34. 10
      app/src/redux/reducers/userReducer.js
  35. 27
      app/src/redux/sagas/drizzleUtilsSaga.js
  36. 52
      app/src/redux/sagas/orbitSaga.js
  37. 21
      app/src/redux/sagas/rootSaga.js
  38. 70
      app/src/redux/sagas/transactionsSaga.js
  39. 46
      app/src/redux/sagas/userSaga.js
  40. 15
      app/src/redux/store.js
  41. 23
      app/src/router/PrivateRoute.js
  42. 27
      app/src/router/routes.js
  43. 30
      app/src/utils/drizzleUtils.js
  44. 40
      app/src/utils/orbitUtils.js
  45. 41
      app/src/utils/serviceWorker.js
  46. 7
      package.json
  47. 10
      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",

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

File diff suppressed because one or more lines are too long

10
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 (
const FloatingButton = props => (
<div className="action-button" onClick={props.onClick}>
<Button icon color='teal' size='large'>
<Icon name='add'/>
<Button icon color="teal" size="large">
<Icon name="add" />
</Button>
</div>
);
};
export default FloatingButton;

13
app/src/components/LoadingSpinner.js

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

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

23
app/src/components/NewTopicPreview.js

@ -1,7 +1,7 @@
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';
@ -25,7 +25,8 @@ class Post extends Component {
size="52"
className="inline"
src={this.props.user.avatarUrl}
name={this.props.user.username}/>
name={this.props.user.username}
/>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
@ -40,9 +41,13 @@ class Post extends Component {
</span>
</div>
<div className="stretch-space-between">
<span><strong>
Subject: {this.props.subject}
</strong></span>
<span>
<strong>
Subject:
{' '}
{this.props.subject}
</strong>
</span>
</div>
<div className="post-content">
<ReactMarkdown source={this.props.content} />
@ -54,12 +59,10 @@ class Post extends Component {
</div>
);
}
};
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
user: state.user
}
};
});
export default connect(mapStateToProps)(Post);

9
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"}}>
const NotFound = () => (
<div style={{
textAlign: 'center'
}}
>
<img src={pageNotFound} alt="Page not found!" />
</div>
);
};
export default NotFound;

197
app/src/components/Post.js

@ -1,17 +1,16 @@
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) {
@ -29,14 +28,16 @@ class Post extends Component {
postSubject: '',
readyForAnimation: false,
animateOnToggle: true
}
};
}
getBlockchainData() {
if (this.props.postData &&
this.props.orbitDB.orbitdb &&
this.state.fetchPostDataStatus === "pending") {
this.setState({ fetchPostDataStatus: 'fetching' });
if (this.props.postData
&& this.props.orbitDB.orbitdb
&& this.state.fetchPostDataStatus === 'pending') {
this.setState({
fetchPostDataStatus: 'fetching'
});
this.fetchPost(this.props.postID);
}
}
@ -46,18 +47,18 @@ class Post extends Component {
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 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);
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);
})
});
}
}
@ -70,64 +71,110 @@ class Post extends Component {
}
render() {
let avatarView = (this.props.postData
? <UserAvatar
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">
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}>
<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>
<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()}}>
? (
<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"}>
<span className={this.props.postData
!== null ? '' : 'grey-text'}
>
<strong>
{this.props.postData !== null
? this.props.postData.value[2]
:"Username"
: 'Username'
}
</strong>
</span>
<span className="grey-text">
{this.props.postData !== null &&
<TimeAgo date={epochTimeConverter(this.props.postData.value[3])}/>
{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"}>
<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
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
)
: `Subject: ${
this.state.postSubject}`
}
</strong>
</span>
@ -135,11 +182,32 @@ class Post extends Component {
<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
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>
@ -147,20 +215,31 @@ class Post extends Component {
</Grid.Row>
<Grid.Row>
<Grid.Column floated="right" textAlign="right">
<Button icon size='mini' style={{marginRight: "0px"}}>
<Icon name='chevron up' />
<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 icon size="mini">
<Icon name="chevron down" />
</Button>
<Button icon size='mini'
<Button
icon
size="mini"
onClick={this.props.postData
? () => { this.props.navigateTo("/topic/"
+ this.props.postData.value[4] + "/"
+ this.props.postID)}
: () => {}}>
<Icon name='linkify' />
? () => {
this.props.navigateTo(`/topic/${
this.props.postData.value[4]}/${
this.props.postID}`);
}
: () => {}}
>
<Icon name="linkify" />
</Button>
</Grid.Column>
</Grid.Row>
@ -179,26 +258,32 @@ class Post extends Component {
if (this.state.readyForAnimation) {
if (this.postRef) {
setTimeout(() => {
this.postRef.current.scrollIntoView({ block: 'start', behavior: 'smooth' });
this.postRef.current.scrollIntoView(
{
block: 'start', behavior: 'smooth'
},
);
setTimeout(() => {
this.setState({ animateOnToggle: false });
this.setState({
animateOnToggle: false
});
}, 300);
}, 100);
this.setState({ readyForAnimation: false });
this.setState({
readyForAnimation: false
});
}
}
}
}
};
const mapDispatchToProps = dispatch => bindActionCreators({
navigateTo: (location) => push(location)
navigateTo: location => push(location)
}, dispatch);
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
user: state.user,
orbitDB: state.orbit
}
};
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post));

72
app/src/components/PostList.js

@ -4,8 +4,8 @@ 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) {
@ -15,41 +15,23 @@ class PostList extends Component {
this.state = {
dataKeys: []
}
}
getBlockchainData(){
if (this.props.drizzleStatus['initialized']){
let dataKeysShallowCopy = this.state.dataKeys.slice();
let fetchingNewData = false;
this.props.postIDs.forEach( postID => {
if (!this.state.dataKeys[postID]) {
dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(postID);
fetchingNewData = true;
}
})
if (fetchingNewData){
this.setState({
dataKeys: dataKeysShallowCopy
});
}
}
};
}
render() {
const posts = this.props.postIDs.map((postID, index) => {
return (<Post
postData={(this.state.dataKeys[postID] && this.props.contracts[contract][getPostMethod][this.state.dataKeys[postID]])
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={""}
avatarUrl=""
postIndex={index}
postID={postID}
getFocus={this.props.focusOnPost === postID ? true : false}
key={postID} />)
});
getFocus={this.props.focusOnPost === postID}
key={postID}
/>
));
return (
<div>
@ -68,13 +50,33 @@ class PostList extends Component {
componentDidUpdate() {
this.getBlockchainData();
}
};
const mapStateToProps = state => {
return {
getBlockchainData() {
if (this.props.drizzleStatus.initialized) {
const dataKeysShallowCopy = this.state.dataKeys.slice();
let fetchingNewData = false;
this.props.postIDs.forEach((postID) => {
if (!this.state.dataKeys[postID]) {
dataKeysShallowCopy[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(
postID,
);
fetchingNewData = true;
}
});
if (fetchingNewData) {
this.setState({
dataKeys: dataKeysShallowCopy
});
}
}
}
}
const mapStateToProps = state => ({
contracts: state.contracts,
drizzleStatus: state.drizzleStatus
}
};
});
export default connect(mapStateToProps)(PostList);

62
app/src/components/ProfileInformation.js

@ -1,19 +1,20 @@
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 = [{
const callsInfo = [
{
contract: 'Forum',
method: 'getUserDateOfRegister'
}, {
contract: 'Forum',
method: 'getOrbitDBId'
}]
}];
class ProfileInformation extends Component {
constructor(props) {
@ -30,40 +31,48 @@ class ProfileInformation extends Component {
}
getBlockchainData() {
if (this.state.pageStatus === 'initialized' &&
this.props.drizzleStatus['initialized']) {
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' });
this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall(
this.props.address,
);
});
this.setState({
pageStatus: 'loading'
});
}
if (this.state.pageStatus === 'loading') {
var pageStatus = 'loaded';
let 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 });
this.setState({
pageStatus
});
}
}
if (this.state.pageStatus === 'loaded') {
if (this.state.dateOfRegister === '') {
let transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction) {
this.setState({ dateOfRegister: transaction.value });
this.setState({
dateOfRegister: transaction.value
});
}
}
if (this.state.orbitDBId === '') {
let transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction) {
this.setState({ orbitDBId: transaction.value });
this.setState({
orbitDBId: transaction.value
});
}
}
}
@ -72,11 +81,14 @@ class ProfileInformation extends Component {
render() {
return (
<div className="user-info">
{this.props.avatarUrl && <UserAvatar
{this.props.avatarUrl && (
<UserAvatar
size="40"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.username}/>}
name={this.props.username}
/>
)}
<table className="highlight centered responsive-table">
<tbody>
<tr>
@ -99,11 +111,13 @@ class ProfileInformation extends Component {
<td><strong>Number of posts:</strong></td>
<td>{this.props.numberOfPosts}</td>
</tr>
{this.state.dateOfRegister &&
{this.state.dateOfRegister
&& (
<tr>
<td><strong>Member since:</strong></td>
<td>{epochTimeConverter(this.state.dateOfRegister)}</td>
</tr>
)
}
</tbody>
</table>
@ -119,14 +133,12 @@ class ProfileInformation extends Component {
componentDidUpdate() {
this.getBlockchainData();
}
};
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
drizzleStatus: state.drizzleStatus,
contracts: state.contracts,
user: state.user
}
};
});
export default connect(mapStateToProps)(ProfileInformation);

111
app/src/components/Topic.js

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

38
app/src/components/TopicList.js

@ -4,8 +4,8 @@ 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) {
@ -15,20 +15,22 @@ class TopicList extends Component {
this.state = {
dataKeys: []
}
};
}
getBlockchainData() {
if (this.props.drizzleStatus['initialized']){
let dataKeysShallowCopy = this.state.dataKeys.slice();
if (this.props.drizzleStatus.initialized) {
const dataKeysShallowCopy = this.state.dataKeys.slice();
let fetchingNewData = false;
this.props.topicIDs.forEach( topicID => {
this.props.topicIDs.forEach((topicID) => {
if (!this.state.dataKeys[topicID]) {
dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(topicID);
dataKeysShallowCopy[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(
topicID,
);
fetchingNewData = true;
}
})
});
if (fetchingNewData) {
this.setState({
@ -39,14 +41,16 @@ class TopicList extends Component {
}
render() {
const topics = this.props.topicIDs.map( topicID => {
return (<Topic
topicData={(this.state.dataKeys[topicID] && this.props.contracts[contract][getTopicMethod][this.state.dataKeys[topicID]])
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} />)
});
key={topicID}
/>
));
return (
<div className="topics-list">
@ -62,13 +66,11 @@ class TopicList extends Component {
componentDidUpdate() {
this.getBlockchainData();
}
};
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
contracts: state.contracts,
drizzleStatus: state.drizzleStatus
}
};
});
export default connect(mapStateToProps)(TopicList);

4
app/src/config/drizzleOptions.js

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

4
app/src/config/ipfsOptions.js

@ -3,11 +3,13 @@
const ipfsOptions = {
EXPERIMENTAL: {
pubsub: true
}, config: {
},
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'
]
}

73
app/src/containers/BoardContainer.js

@ -1,17 +1,17 @@
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'; */
const contract = "Forum";
const getNumberOfTopicsMethod = "getNumberOfTopics";
const contract = 'Forum';
const getNumberOfTopicsMethod = 'getNumberOfTopics';
class BoardContainer extends Component {
constructor(props) {
@ -24,51 +24,58 @@ class BoardContainer extends Component {
this.state = {
pageStatus: 'initialized'
}
};
}
getBlockchainData() {
if (this.state.pageStatus === 'initialized' &&
this.props.drizzleStatus['initialized']){
if (this.state.pageStatus === 'initialized'
&& this.props.drizzleStatus.initialized) {
this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall();
this.setState({ pageStatus: 'loading' });
this.setState({
pageStatus: 'loading'
});
}
if (this.state.pageStatus === 'loading' &&
this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]){
this.setState({ pageStatus: 'loaded' });
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");
this.props.history.push('/startTopic');
}
render() {
var boardContents;
let boardContents;
if (this.state.pageStatus === 'loaded') {
var numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value;
const numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value;
if (numberOfTopics !== '0') {
this.topicIDs = [];
for (var i = 0; i < numberOfTopics; i++) {
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"></div>,
this.props.hasSignedUp &&
<FloatingButton onClick={this.handleCreateTopicClick}
key="createTopicButton"/>
<div className="bottom-overlay-pad" key="pad" />,
this.props.hasSignedUp
&& (
<FloatingButton
onClick={this.handleCreateTopicClick}
key="createTopicButton"
/>
)
]);
} else {
if (!this.props.hasSignedUp){
} else if (!this.props.hasSignedUp) {
boardContents = (
<div className="vertical-center-in-parent">
<Header color='teal' textAlign='center' as='h2'>
<Header color="teal" textAlign="center" as="h2">
There are no topics yet!
</Header>
<Header color='teal' textAlign='center' as='h4'>
<Header color="teal" textAlign="center" as="h4">
Sign up to be the first to post.
</Header>
</div>
@ -76,19 +83,21 @@ class BoardContainer extends Component {
} else {
boardContents = (
<div className="vertical-center-in-parent">
<Header color='teal' textAlign='center' as='h2'>
<Header color="teal" textAlign="center" as="h2">
There are no topics yet!
</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 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"/>
<FloatingButton
onClick={this.handleCreateTopicClick}
key="createTopicButton"
/>
</div>
);
}
}
}
return (
<div className="fill">
@ -106,12 +115,10 @@ class BoardContainer extends Component {
}
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
contracts: state.contracts,
drizzleStatus: state.drizzleStatus,
hasSignedUp: state.user.hasSignedUp
}
};
});
export default withRouter(connect(mapStateToProps)(BoardContainer));

9
app/src/containers/CoreLayoutContainer.js

@ -2,8 +2,6 @@ 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/css/App.css';
@ -14,6 +12,8 @@ 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 (
@ -26,8 +26,7 @@ class CoreLayout extends Component {
</div>
</div> */}
<div className="page-container">
<aside className="left-side-panel">
</aside>
<aside className="left-side-panel" />
<div className="main-panel">
<div className="view-container">
{this.props.children}
@ -42,4 +41,4 @@ class CoreLayout extends Component {
}
}
export default CoreLayout
export default CoreLayout;

42
app/src/containers/NavBarContainer.js

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

109
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,7 +12,8 @@ import PostList from '../components/PostList';
import LoadingSpinner from '../components/LoadingSpinner';
import { setNavBarTitle } from '../redux/actions/userInterfaceActions';
const callsInfo = [{
const callsInfo = [
{
contract: 'Forum',
method: 'getUsername'
}, {
@ -31,7 +32,7 @@ class ProfileContainer extends Component {
this.getBlockchainData = this.getBlockchainData.bind(this);
this.dataKey = [];
var address = this.props.match.params.address
const address = this.props.match.params.address
? this.props.match.params.address
: this.props.user.address;
@ -45,48 +46,58 @@ class ProfileContainer extends Component {
}
getBlockchainData() {
if (this.state.pageStatus === 'initialized' &&
this.props.drizzleStatus['initialized']) {
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' });
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';
let 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 });
this.setState({
pageStatus
});
}
}
if (this.state.pageStatus === 'loaded') {
if (this.state.username === '') {
let transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]];
if (transaction) {
var username = transaction.value;
const username = transaction.value;
this.props.setNavBarTitle(username);
this.setState({ username: username });
this.setState({
username
});
}
}
if (this.state.topicIDs === null) {
let transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]];
if (transaction) {
this.setState({ topicIDs: transaction.value });
this.setState({
topicIDs: transaction.value
});
}
}
if (this.state.postIDs === null) {
let transaction = this.props.contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]];
const transaction = this.props.contracts[callsInfo[2].contract][callsInfo[2].method][this.dataKey[2]];
if (transaction) {
this.setState({ postIDs: transaction.value });
this.setState({
postIDs: transaction.value
});
}
}
@ -96,64 +107,70 @@ class ProfileContainer extends Component {
render() {
if (!this.props.user.hasSignedUp) {
this.props.navigateTo("/signup");
this.props.navigateTo('/signup');
return (null);
}
var infoTab =
(<ProfileInformation
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"
/>);
var topicsTab =
(<div className="profile-tab">
/>
);
const topicsTab = (
<div className="profile-tab">
{this.state.topicIDs
? <TopicList topicIDs={this.state.topicIDs} />
: <LoadingSpinner />
}
</div>);
var postsTab =
(<div className="profile-tab">
</div>
);
const postsTab = (
<div className="profile-tab">
{this.state.postIDs
? <PostList postIDs={this.state.postIDs} recentToTheTop />
: <LoadingSpinner />
}
</div>);
</div>
);
const profilePanes = [
{
menuItem: 'INFORMATION',
pane: {
key: 'INFORMATION',
content: (infoTab),
},
content: (infoTab)
}
},
{
menuItem: 'TOPICS',
pane: {
key: 'TOPICS',
content: (topicsTab),
},
content: (topicsTab)
}
},
{
menuItem: 'POSTS',
pane: {
key: 'POSTS',
content: (postsTab),
},
},
]
content: (postsTab)
}
}
];
return (
<div>
<Tab
menu={{ secondary: true, pointing: true }}
menu={{
secondary: true, pointing: true
}}
panes={profilePanes}
renderActiveOnly={false} />
renderActiveOnly={false}
/>
</div>
);
}
@ -172,17 +189,15 @@ class ProfileContainer extends Component {
}
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 {
const mapStateToProps = state => ({
user: state.user,
drizzleStatus: state.drizzleStatus,
contracts: state.contracts,
orbitDB: state.orbitDB
}
};
});
export default connect(mapStateToProps, mapDispatchToProps)(ProfileContainer);

31
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("/");
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'>
? (
<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'>
<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>
)
: (
<div className="sign-up-container">
<div>
<h1>Sign Up</h1>
<p className="no-margin">
<strong>Account address:</strong> {this.props.user.address}
<strong>Account address:</strong>
{' '}
{this.props.user.address}
</p>
<UsernameFormContainer />
</div>
</div>)
</div>
)
);
}
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
user: state.user
}
};
});
export default connect(mapStateToProps)(SignUpContainer);

89
app/src/containers/StartTopicContainer.js

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

113
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,8 +10,8 @@ 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) {
@ -31,7 +31,8 @@ class TopicContainer extends Component {
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)
postFocus: this.props.match.params.postId
&& /^[0-9]+$/.test(this.props.match.params.postId)
? this.props.match.params.postId
: null,
fetchTopicSubjectStatus: 'pending',
@ -40,50 +41,65 @@ class TopicContainer extends Component {
}
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.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' });
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' });
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'
});
}
}
async fetchTopicSubject(orbitDBAddress) {
let orbitData;
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1] === this.props.user.address) {
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1]
=== this.props.user.address) {
orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID);
} else {
const fullAddress = "/orbitdb/" + orbitDBAddress + "/topics";
const fullAddress = `/orbitdb/${orbitDBAddress}/topics`;
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
await store.load();
let localOrbitData = store.get(this.state.topicID);
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);
})
});
}
}
this.props.setNavBarTitle(orbitData['subject']);
this.props.setNavBarTitle(orbitData.subject);
this.setState({
topicSubject: orbitData['subject'],
topicSubject: orbitData.subject,
fetchTopicSubjectStatus: 'fetched'
});
}
@ -104,33 +120,42 @@ class TopicContainer extends Component {
}
render() {
var topicContents;
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}
(
<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()}}
onCancelClick={() => { this.togglePostingState(); }}
onPostCreated={() => { this.postCreated(); }}
/>
)
}
<div className="posts-list-spacer"></div>
{this.props.user.hasSignedUp && !this.state.posting &&
<FloatingButton onClick={this.togglePostingState}/>
<div className="posts-list-spacer" />
{this.props.user.hasSignedUp && !this.state.posting
&& <FloatingButton onClick={this.togglePostingState} />
}
</div>)
</div>
)
);
}
return (
<div className="fill">
{topicContents}
{!this.state.posting &&
<div className="bottom-overlay-pad"></div>
{!this.state.posting
&& <div className="bottom-overlay-pad" />
}
</div>
);
@ -150,17 +175,15 @@ class TopicContainer extends Component {
}
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 {
const mapStateToProps = state => ({
user: state.user,
contracts: state.contracts,
drizzleStatus: state.drizzleStatus,
orbitDB: state.orbit
}
};
});
export default connect(mapStateToProps, mapDispatchToProps)(TopicContainer);

91
app/src/containers/TransactionsMonitorContainer.js

@ -1,6 +1,6 @@
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';
@ -13,38 +13,37 @@ class RightSideBar extends Component {
this.state = {
isTransactionMessageDismissed: []
}
};
}
handleMessageClick(index) {
let transactionHash = this.props.transactionStack[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]){
} 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.props.history.push('/profile');
this.handleMessageDismiss(null, index);
break;
case 'UsernameUpdated':
this.props.history.push("/profile");
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.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.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:
@ -54,14 +53,13 @@ class RightSideBar extends Component {
}
}
}
}
handleMessageDismiss(event, messageIndex) {
if (event !== null) {
event.stopPropagation();
}
let isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice();
const isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice();
isTransactionMessageDismissedShallowCopy[messageIndex] = true;
this.setState({
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy
@ -73,58 +71,69 @@ class RightSideBar extends Component {
return null;
}
let transactionMessages = this.props.transactionStack.map((transaction, index) => {
const 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.");
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");
message.push('- transaction confirmed');
}
if (this.props.transactions[transaction] &&
this.props.transactions[transaction].status === 'success') {
if (this.props.transactions[transaction]
&& this.props.transactions[transaction].status === 'success') {
/* Transaction completed successfully */
message.push(<br key="mined" />);
message.push("- transaction 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"){
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");
message.push('- transaction mined');
color = 'red';
message.push(<br key="fail" />);
message.push("Transaction failed to complete!");
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)}}>
<div
className="sidebar-message"
key={index}
onClick={() => { this.handleMessageClick(index); }}
>
<Message
color={color}
onDismiss={(e) => {
this.handleMessageDismiss(e, index);
}}
>
{message}
</Message>
</div>
);
});
},
);
return (transactionMessages);
}
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
transactions: state.transactions,
transactionStack: state.transactionStack
}
};
});
const RightSideBarContainer = withRouter(connect(mapStateToProps)(RightSideBar));
const RightSideBarContainer = withRouter(
connect(mapStateToProps)(RightSideBar),
);
export default RightSideBarContainer;

103
app/src/containers/UsernameFormContainer.js

@ -1,15 +1,15 @@
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) {
@ -23,8 +23,8 @@ class UsernameFormContainer extends Component {
this.state = {
usernameInput: '',
error: false,
errorHeader: "",
errorMessage: "",
errorHeader: '',
errorMessage: '',
signingUp: false
};
}
@ -41,7 +41,9 @@ class UsernameFormContainer extends Component {
}
}
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(value);
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(
value,
);
}
}
@ -49,12 +51,14 @@ class UsernameFormContainer extends Component {
if (this.state.usernameInput === '') {
this.setState({
error: true,
errorHeader: "Data Incomplete",
errorMessage: "You need to provide a username"
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)){
if (this.checkedUsernames.some(
e => e.usernameChecked === this.state.usernameInput,
)) {
this.completeAction();
}
}
@ -64,10 +68,13 @@ class UsernameFormContainer extends Component {
if (this.props.user.hasSignedUp) {
this.props.dispatch(updateUsername(...[this.state.usernameInput], null));
} else {
this.setState({ signingUp: true });
this.setState({
signingUp: true
});
const orbitdbInfo = await createDatabases();
this.stackId = drizzle.contracts[contract].methods[signUpMethod]
.cacheSend(...[this.state.usernameInput,
this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend(
...[
this.state.usernameInput,
orbitdbInfo.identityId,
orbitdbInfo.identityPublicKey,
orbitdbInfo.identityPrivateKey,
@ -76,48 +83,61 @@ class UsernameFormContainer extends Component {
orbitdbInfo.orbitPrivateKey,
orbitdbInfo.topicsDB,
orbitdbInfo.postsDB
], { from: this.props.account});
], {
from: this.props.account
},
);
}
this.setState({ usernameInput: '' });
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});
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 {
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.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"
errorHeader: 'Data disapproved',
errorMessage: 'This username is already taken'
});
}
})
});
}
}
}
render() {
const hasSignedUp = this.props.user.hasSignedUp;
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};
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) {
@ -137,7 +157,7 @@ class UsernameFormContainer extends Component {
<label>Username</label>
<Form.Input
placeholder={placeholderText}
name='usernameInput'
name="usernameInput"
value={this.state.usernameInput}
onChange={this.handleInputChange}
/>
@ -147,11 +167,14 @@ class UsernameFormContainer extends Component {
header={this.state.errorHeader}
content={this.state.errorMessage}
/>
<Button type='submit'>{buttonText}</Button>
<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 as="h2" inverted>
<Loader size="large">
Magic elves are processing your noble
request.
</Loader>
</Header>
</Dimmer>
</div>
@ -162,14 +185,12 @@ class UsernameFormContainer extends Component {
}
}
const mapStateToProps = state => {
return {
const mapStateToProps = state => ({
account: state.accounts[0],
contracts: state.contracts,
transactions: state.transactions,
transactionStack: state.transactionStack,
user: state.user
}
};
});
export default connect(mapStateToProps)(UsernameFormContainer);

16
app/src/helpers/EpochTimeConverter.js

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

10
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 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();
@ -24,7 +24,7 @@ render(
{routes}
</ConnectedRouter>
</Provider>,
document.getElementById('root')
document.getElementById('root'),
);
serviceWorker.unregister(); // See also: http://bit.ly/CRA-PWA

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

@ -13,4 +13,8 @@ function updateDatabases(type, orbitdb, topicsDB, postsDB){
};
}
export { DATABASES_CREATED, DATABASES_LOADED, DATABASES_NOT_READY, IPFS_INITIALIZED, updateDatabases }
export { DATABASES_CREATED,
DATABASES_LOADED,
DATABASES_NOT_READY,
IPFS_INITIALIZED,
updateDatabases };

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

@ -13,7 +13,7 @@ export function updateUsername(newUsername, callback){
params: [newUsername],
event: 'UsernameUpdated'
},
callback: callback
callback
};
}
@ -27,7 +27,7 @@ export function createTopic(userInputs){
params: [],
event: 'TopicCreated'
},
userInputs: userInputs
userInputs
};
}
@ -41,7 +41,7 @@ export function createPost(topicID, userInputs){
params: [topicID],
event: 'PostCreated'
},
userInputs: userInputs
userInputs
};
}

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

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

6
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({
export default history => combineReducers({
router: connectRouter(history),
user: userReducer,
orbit: orbitReducer,
interface: userInterfaceReducer,
...drizzleReducers
})
});

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

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

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

@ -1,25 +1,24 @@
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
web3, artifact: Forum
});
initFlag = true;
yield put({type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]});
}
else
console.warn("Attempted to reinitialize drizzleUtilsSaga!");
yield put({
type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]
});
} else console.warn('Attempted to reinitialize drizzleUtilsSaga!');
}
// If the method below proves to be problematic/ineffective (i.e. getting current account
@ -30,9 +29,9 @@ function* getCurrentAccount(){
}
function* drizzleUtilsSaga() {
yield takeLatest("DRIZZLE_INITIALIZED", init);
yield takeLatest('DRIZZLE_INITIALIZED', init);
}
export { web3, contract, getCurrentAccount }
export { web3, contract, getCurrentAccount };
export default drizzleUtilsSaga;

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

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

21
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];
const sagas = [
...drizzleSagas,
drizzleUtilsSaga,
orbitSaga,
userSaga,
transactionsSaga];
yield all(
sagas.map(saga => fork(saga))
)
sagas.map(saga => fork(saga)),
);
}

70
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';
}
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') {
if (dataKey !== -1
&& transactionsHistory[dataKey]
&& transactionsHistory[dataKey].state === 'initialized') {
transactionsHistory[dataKey].state = 'success';
// Gets orbit
const orbit = yield select((state) => state.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 });
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') {
if (dataKey !== -1
&& transactionsHistory[dataKey]
&& transactionsHistory[dataKey].state === 'initialized') {
transactionsHistory[dataKey].state = 'success';
// Gets orbit
const orbit = yield select((state) => state.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 });
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;
}
}
function* handleError() {
var transactionStack = yield select((state) => state.transactionStack);
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 takeEvery('INIT_TRANSACTION', initTransaction);
yield takeEvery('EVENT_FIRED', handleEvent);
yield takeEvery('TX_ERROR', handleError);
}
export default transactionsSaga;

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

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

15
app/src/redux/store.js

@ -1,5 +1,5 @@
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';
@ -8,23 +8,26 @@ 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
composedEnhancers,
);
sagaMiddleware.run(rootSaga);

23
app/src/router/PrivateRoute.js

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

27
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="/" />
<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
path="/profile/:address?/:username?"
component={ProfileContainer}
/>
<Route path="/404" component={NotFound} />
<Route component={NotFound} />
</Switch>
</CoreLayoutContainer>
</div>
);
export default routes
export default routes;

30
app/src/utils/drizzleUtils.js

@ -1,5 +1,5 @@
// See also: https://github.com/trufflesuite/drizzle-utils
const Web3 = require("web3");
const Web3 = require('web3');
const resolveWeb3 = (resolve, options, isBrowser) => {
let provider;
@ -10,7 +10,7 @@ const resolveWeb3 = (resolve, options, isBrowser) => {
} else if (isBrowser && window.ethereum) {
// use `ethereum` object injected by MetaMask
provider = window.ethereum;
} else if (isBrowser && typeof window.web3 !== "undefined") {
} else if (isBrowser && typeof window.web3 !== 'undefined') {
// use injected web3 object by legacy dapp browsers
provider = window.web3.currentProvider;
} else if (options.fallbackProvider) {
@ -18,38 +18,36 @@ const resolveWeb3 = (resolve, options, isBrowser) => {
provider = options.fallbackProvider;
} else {
// connect to development blockchain from `truffle develop`
provider = new Web3.providers.HttpProvider("http://127.0.0.1:9545");
provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545');
}
const web3 = new Web3(provider);
resolve(web3);
};
const getWeb3 = (options = {}) =>
new Promise(resolve => {
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";
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`) {
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),
);
return window.addEventListener('load', () => resolveWeb3(resolve, options, true));
});
const getContractInstance = (options = {}) =>
new Promise(async (resolve, reject) => {
const getContractInstance = (options = {
}) => new Promise(async (resolve, reject) => {
if (!options.web3) {
return reject(new Error("The options object with web3 is required."));
return reject(new Error('The options object with web3 is required.'));
}
const { web3 } = options;
@ -74,7 +72,7 @@ const getContractInstance = (options = {}) =>
} else {
return reject(
new Error(
"You must pass in a contract artifact or the ABI of a deployed contract.",
'You must pass in a contract artifact or the ABI of a deployed contract.',
),
);
}

40
app/src/utils/orbitUtils.js

@ -1,33 +1,37 @@
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.");
store.dispatch({
type: IPFS_INITIALIZED, ipfs
});
console.log('IPFS initialized.');
});
}
async function createDatabases() {
console.log("Creating databases...");
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));
store.dispatch(
updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB),
);
const orbitKey = orbitdb.keystore.getKey(orbitdb.id);
return {
identityId: "Tempus",
identityPublicKey: "edax",
identityPrivateKey: "rerum",
identityId: 'Tempus',
identityPublicKey: 'edax',
identityPrivateKey: 'rerum',
orbitId: orbitdb.id,
orbitPublicKey: orbitKey.getPublic('hex'),
orbitPrivateKey: orbitKey.getPrivate('hex'),
@ -37,10 +41,11 @@ async function createDatabases() {
}
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,
@ -48,9 +53,12 @@ async function loadDatabases(identityId, identityPublicKey, identityPrivateKey,
}));
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 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();

41
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,9 +55,7 @@ export function register(config) {
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
navigator.serviceWorker.register(swUrl).then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
@ -70,8 +68,8 @@ function registerValidSW(swUrl, config) {
// 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.'
'New content is available and will be used when all '
+ 'tabs for this page are closed. See http://bit.ly/CRA-PWA.',
);
// Execute callback
@ -92,24 +90,22 @@ function registerValidSW(swUrl, config) {
}
};
};
})
.catch(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 => {
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)
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 => {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
@ -118,17 +114,16 @@ function checkValidServiceWorker(swUrl, config) {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
}).catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
'No internet connection found. App is running in offline mode.',
);
});
}
export function unregister() {
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"
}
}

10
truffle-config.js

@ -1,19 +1,19 @@
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"),
contracts_build_directory: path.join(__dirname, 'app/src/contracts'),
networks: {
development: {
host: "localhost",
host: 'localhost',
port: 8545,
network_id: "*" // Match any network id
network_id: '*' // Match any network id
}
},
compilers: {
solc: {
version: "0.5.5",
version: '0.5.5',
settings: {
optimizer: {
enabled: true,

Loading…
Cancel
Save