mirror of https://gitlab.com/ecentrics/concordia
Ezerous
6 years ago
56 changed files with 12421 additions and 4324 deletions
@ -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" |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
# node_modules in the project root is ignored by default |
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"extends": "plugin:react/recommended" |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
node_modules/* |
@ -1,30 +1,30 @@ |
|||||
/* TOPICS LIST SCREEN */ |
/* TOPICS LIST SCREEN */ |
||||
|
|
||||
.topics-list { |
.topics-list { |
||||
padding: 0px 2px; |
padding: 0px 2px; |
||||
margin-bottom: 75px; |
margin-bottom: 75px; |
||||
} |
} |
||||
|
|
||||
.topics-list a { |
.topics-list a { |
||||
color: black !important; |
color: black !important; |
||||
text-decoration: none !important; |
text-decoration: none !important; |
||||
} |
} |
||||
|
|
||||
.topics-list a:hover { |
.topics-list a:hover { |
||||
color: black !important; |
color: black !important; |
||||
text-decoration: none !important; |
text-decoration: none !important; |
||||
} |
} |
||||
|
|
||||
.topic-subject { |
.topic-subject { |
||||
margin: 0px 0px 5px; |
margin: 0px 0px 5px; |
||||
} |
} |
||||
|
|
||||
.topic-meta { |
.topic-meta { |
||||
margin: 5px 0px 0px; |
margin: 5px 0px 0px; |
||||
} |
} |
||||
|
|
||||
.topic-date { |
.topic-date { |
||||
margin-bottom: 0px; |
margin-bottom: 0px; |
||||
font-size: 0.77vw !important; |
font-size: 0.77vw !important; |
||||
text-align: right; |
text-align: right; |
||||
} |
} |
@ -1,5 +1,5 @@ |
|||||
/* PROFILE SCREEN */ |
/* PROFILE SCREEN */ |
||||
|
|
||||
.profile-tab { |
.profile-tab { |
||||
width: 100%; |
width: 100%; |
||||
} |
} |
@ -1,12 +1,12 @@ |
|||||
/* SIGN UP SCREEN */ |
/* SIGN UP SCREEN */ |
||||
|
|
||||
.sign-up-container { |
.sign-up-container { |
||||
height: 100%; |
height: 100%; |
||||
display: flex; |
display: flex; |
||||
flex-direction: column; |
flex-direction: column; |
||||
justify-content: center; |
justify-content: center; |
||||
} |
} |
||||
|
|
||||
.sign-up-container>div { |
.sign-up-container > div { |
||||
margin: auto; |
margin: auto; |
||||
} |
} |
@ -1,6 +1,6 @@ |
|||||
/* START TOPIC SCREEN */ |
/* START TOPIC SCREEN */ |
||||
|
|
||||
.topic-form { |
.topic-form { |
||||
width: 100%; |
width: 100%; |
||||
margin: 20px 0px; |
margin: 20px 0px; |
||||
} |
} |
@ -1,51 +1,51 @@ |
|||||
/* POSTS LIST SCREEN */ |
/* POSTS LIST SCREEN */ |
||||
|
|
||||
.posts-list-spacer { |
.posts-list-spacer { |
||||
margin-bottom: 85px; |
margin-bottom: 85px; |
||||
height: 0px; |
height: 0px; |
||||
} |
} |
||||
|
|
||||
.post { |
.post { |
||||
width: 100%; |
width: 100%; |
||||
background-color: #FFFFFF; |
background-color: #FFFFFF; |
||||
margin: 20px 0px; |
margin: 20px 0px; |
||||
padding: 0px; |
padding: 0px; |
||||
} |
} |
||||
|
|
||||
.post-meta { |
.post-meta { |
||||
float: right; |
float: right; |
||||
margin-right: 11.25px; |
margin-right: 11.25px; |
||||
} |
} |
||||
|
|
||||
.user-avatar { |
.user-avatar { |
||||
width: 52px; |
width: 52px; |
||||
height: 52px; |
height: 52px; |
||||
text-align: center; |
text-align: center; |
||||
} |
} |
||||
|
|
||||
.user-avatar a { |
.user-avatar a { |
||||
color: inherit !important; |
color: inherit !important; |
||||
text-decoration: none !important; |
text-decoration: none !important; |
||||
} |
} |
||||
|
|
||||
.stretch-space-between { |
.stretch-space-between { |
||||
display: flex; |
display: flex; |
||||
flex-flow: row nowrap; |
flex-flow: row nowrap; |
||||
justify-content: space-between; |
justify-content: space-between; |
||||
} |
} |
||||
|
|
||||
.user-info { |
.user-info { |
||||
background-color: #FFFFFF; |
background-color: #FFFFFF; |
||||
margin: 12px auto; |
margin: 12px auto; |
||||
padding: 7px; |
padding: 7px; |
||||
} |
} |
||||
|
|
||||
.post-content a{ |
.post-content a { |
||||
margin-top: 10px; |
margin-top: 10px; |
||||
color: #039be5; |
color: #039be5; |
||||
} |
} |
||||
|
|
||||
.post-form { |
.post-form { |
||||
width: 100%; |
width: 100%; |
||||
margin: 20px 0px; |
margin: 20px 0px; |
||||
} |
} |
File diff suppressed because one or more lines are too long
@ -1,14 +1,12 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
import { Button, Icon } from 'semantic-ui-react' |
import { Button, Icon } from 'semantic-ui-react'; |
||||
|
|
||||
const FloatingButton = (props) => { |
const FloatingButton = props => ( |
||||
return ( |
<div className="action-button" onClick={props.onClick}> |
||||
<div className="action-button" onClick={props.onClick}> |
<Button icon color="teal" size="large"> |
||||
<Button icon color='teal' size='large'> |
<Icon name="add" /> |
||||
<Icon name='add'/> |
</Button> |
||||
</Button> |
</div> |
||||
</div> |
); |
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default FloatingButton; |
export default FloatingButton; |
@ -1,16 +1,17 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
|
|
||||
const LoadingSpinner = (props) => { |
const LoadingSpinner = props => ( |
||||
return( |
<div className="vertical-center-children"> |
||||
<div className="vertical-center-children"> |
<div |
||||
<div className={"center-in-parent " + (props.className ? props.className : "")} |
className={`center-in-parent ${ |
||||
style={props.style ? props.style : []}> |
props.className ? props.className : ''}`}
|
||||
<p> |
style={props.style ? props.style : []} |
||||
<i className="fas fa-spinner fa-3x fa-spin"></i> |
> |
||||
</p> |
<p> |
||||
</div> |
<i className="fas fa-spinner fa-3x fa-spin" /> |
||||
</div> |
</p> |
||||
); |
</div> |
||||
} |
</div> |
||||
|
); |
||||
|
|
||||
export default LoadingSpinner; |
export default LoadingSpinner; |
@ -1,65 +1,68 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
|
|
||||
import { Grid, Divider } from 'semantic-ui-react' |
import { Divider, Grid } from 'semantic-ui-react'; |
||||
|
|
||||
import TimeAgo from 'react-timeago'; |
import TimeAgo from 'react-timeago'; |
||||
import UserAvatar from 'react-user-avatar'; |
import UserAvatar from 'react-user-avatar'; |
||||
import ReactMarkdown from 'react-markdown'; |
import ReactMarkdown from 'react-markdown'; |
||||
|
|
||||
class Post extends Component { |
class Post extends Component { |
||||
constructor(props, context) { |
constructor(props, context) { |
||||
super(props); |
super(props); |
||||
} |
} |
||||
|
|
||||
render(){ |
render() { |
||||
return ( |
return ( |
||||
<div className="post"> |
<div className="post"> |
||||
<Divider horizontal> |
<Divider horizontal> |
||||
<span className="grey-text">#0</span> |
<span className="grey-text">#0</span> |
||||
</Divider> |
</Divider> |
||||
<Grid> |
<Grid> |
||||
<Grid.Row columns={16} stretched> |
<Grid.Row columns={16} stretched> |
||||
<Grid.Column width={1} className="user-avatar"> |
<Grid.Column width={1} className="user-avatar"> |
||||
<UserAvatar |
<UserAvatar |
||||
size="52" |
size="52" |
||||
className="inline" |
className="inline" |
||||
src={this.props.user.avatarUrl} |
src={this.props.user.avatarUrl} |
||||
name={this.props.user.username}/> |
name={this.props.user.username} |
||||
</Grid.Column> |
/> |
||||
<Grid.Column width={15}> |
</Grid.Column> |
||||
<div className=""> |
<Grid.Column width={15}> |
||||
<div className="stretch-space-between"> |
<div className=""> |
||||
<span> |
<div className="stretch-space-between"> |
||||
<strong> |
<span> |
||||
{this.props.user.username} |
<strong> |
||||
</strong> |
{this.props.user.username} |
||||
</span> |
</strong> |
||||
<span className="grey-text"> |
</span> |
||||
<TimeAgo date={this.props.date}/> |
<span className="grey-text"> |
||||
</span> |
<TimeAgo date={this.props.date} /> |
||||
</div> |
</span> |
||||
<div className="stretch-space-between"> |
</div> |
||||
<span><strong> |
<div className="stretch-space-between"> |
||||
Subject: {this.props.subject} |
<span> |
||||
</strong></span> |
<strong> |
||||
</div> |
Subject: |
||||
<div className="post-content"> |
{' '} |
||||
<ReactMarkdown source={this.props.content} /> |
{this.props.subject} |
||||
</div> |
</strong> |
||||
</div> |
</span> |
||||
</Grid.Column> |
</div> |
||||
</Grid.Row> |
<div className="post-content"> |
||||
</Grid> |
<ReactMarkdown source={this.props.content} /> |
||||
</div> |
</div> |
||||
); |
</div> |
||||
} |
</Grid.Column> |
||||
}; |
</Grid.Row> |
||||
|
</Grid> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
user: state.user |
||||
user: state.user |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(Post); |
export default connect(mapStateToProps)(Post); |
@ -1,12 +1,13 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
import pageNotFound from '../assets/images/PageNotFound.jpg'; |
import pageNotFound from '../assets/images/PageNotFound.jpg'; |
||||
|
|
||||
const NotFound = () => { |
const NotFound = () => ( |
||||
return ( |
<div style={{ |
||||
<div style={{textAlign: "center"}}> |
textAlign: 'center' |
||||
<img src={pageNotFound} alt="Page not found!"/> |
}} |
||||
</div> |
> |
||||
); |
<img src={pageNotFound} alt="Page not found!" /> |
||||
}; |
</div> |
||||
|
); |
||||
|
|
||||
export default NotFound; |
export default NotFound; |
@ -1,204 +1,289 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { bindActionCreators } from 'redux'; |
import { bindActionCreators } from 'redux'; |
||||
import { push } from 'connected-react-router' |
import { push } from 'connected-react-router'; |
||||
import { Link, withRouter } from 'react-router-dom'; |
import { Link, withRouter } from 'react-router-dom'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
|
|
||||
import ContentLoader from "react-content-loader" |
import ContentLoader from 'react-content-loader'; |
||||
import { Transition } from 'semantic-ui-react' |
import { Button, Divider, Grid, Icon, Label, Transition } from 'semantic-ui-react'; |
||||
import { Grid, Divider, Button, Icon, Label } from 'semantic-ui-react' |
|
||||
|
|
||||
import TimeAgo from 'react-timeago'; |
import TimeAgo from 'react-timeago'; |
||||
import epochTimeConverter from '../helpers/EpochTimeConverter'; |
|
||||
import UserAvatar from 'react-user-avatar'; |
import UserAvatar from 'react-user-avatar'; |
||||
import ReactMarkdown from 'react-markdown'; |
import ReactMarkdown from 'react-markdown'; |
||||
|
import epochTimeConverter from '../helpers/EpochTimeConverter'; |
||||
|
|
||||
class Post extends Component { |
class Post extends Component { |
||||
constructor(props) { |
constructor(props) { |
||||
super(props); |
super(props); |
||||
|
|
||||
this.getBlockchainData = this.getBlockchainData.bind(this); |
this.getBlockchainData = this.getBlockchainData.bind(this); |
||||
this.fetchPost = this.fetchPost.bind(this); |
this.fetchPost = this.fetchPost.bind(this); |
||||
if (props.getFocus){ |
if (props.getFocus) { |
||||
this.postRef = React.createRef(); |
this.postRef = React.createRef(); |
||||
} |
|
||||
|
|
||||
this.state = { |
|
||||
fetchPostDataStatus: 'pending', |
|
||||
postContent: '', |
|
||||
postSubject: '', |
|
||||
readyForAnimation: false, |
|
||||
animateOnToggle: true |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
getBlockchainData() { |
this.state = { |
||||
if (this.props.postData && |
fetchPostDataStatus: 'pending', |
||||
this.props.orbitDB.orbitdb && |
postContent: '', |
||||
this.state.fetchPostDataStatus === "pending") { |
postSubject: '', |
||||
this.setState({ fetchPostDataStatus: 'fetching' }); |
readyForAnimation: false, |
||||
this.fetchPost(this.props.postID); |
animateOnToggle: true |
||||
} |
}; |
||||
|
} |
||||
|
|
||||
|
getBlockchainData() { |
||||
|
if (this.props.postData |
||||
|
&& this.props.orbitDB.orbitdb |
||||
|
&& this.state.fetchPostDataStatus === 'pending') { |
||||
|
this.setState({ |
||||
|
fetchPostDataStatus: 'fetching' |
||||
|
}); |
||||
|
this.fetchPost(this.props.postID); |
||||
} |
} |
||||
|
} |
||||
|
|
||||
async fetchPost(postID) { |
async fetchPost(postID) { |
||||
let orbitPostData; |
let orbitPostData; |
||||
if (this.props.postData.value[1] === this.props.user.address) { |
if (this.props.postData.value[1] === this.props.user.address) { |
||||
orbitPostData = this.props.orbitDB.postsDB.get(postID); |
orbitPostData = this.props.orbitDB.postsDB.get(postID); |
||||
} else { |
} else { |
||||
const fullAddress = "/orbitdb/" + this.props.postData.value[0] + "/posts"; |
const fullAddress = `/orbitdb/${this.props.postData.value[0]}/posts`; |
||||
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
||||
await store.load(); |
await store.load(); |
||||
|
|
||||
let localOrbitData = store.get(postID); |
|
||||
if (localOrbitData) { |
|
||||
orbitPostData = localOrbitData; |
|
||||
} else { |
|
||||
// Wait until we have received something from the network
|
|
||||
store.events.on('replicated', () => { |
|
||||
orbitPostData = store.get(postID); |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.setState({ |
const localOrbitData = store.get(postID); |
||||
postContent: orbitPostData.content, |
if (localOrbitData) { |
||||
postSubject: orbitPostData.subject, |
orbitPostData = localOrbitData; |
||||
fetchPostDataStatus: 'fetched', |
} else { |
||||
readyForAnimation: true |
// Wait until we have received something from the network
|
||||
|
store.events.on('replicated', () => { |
||||
|
orbitPostData = store.get(postID); |
||||
}); |
}); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
render(){ |
this.setState({ |
||||
let avatarView = (this.props.postData |
postContent: orbitPostData.content, |
||||
? <UserAvatar |
postSubject: orbitPostData.subject, |
||||
size="52" |
fetchPostDataStatus: 'fetched', |
||||
className="inline" |
readyForAnimation: true |
||||
src={this.props.avatarUrl} |
}); |
||||
name={this.props.postData.value[2]}/> |
} |
||||
: <div className="user-avatar"> |
|
||||
<ContentLoader height={52} width={52} speed={2} |
render() { |
||||
primaryColor="#b2e8e6" secondaryColor="#00b5ad"> |
const avatarView = (this.props.postData |
||||
<circle cx="26" cy="26" r="26" /> |
? ( |
||||
</ContentLoader> |
<UserAvatar |
||||
</div> |
size="52" |
||||
); |
className="inline" |
||||
|
src={this.props.avatarUrl} |
||||
return ( |
name={this.props.postData.value[2]} |
||||
<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> |
<div className="user-avatar"> |
||||
</Divider> |
<ContentLoader |
||||
<Grid> |
height={52} |
||||
<Grid.Row columns={16} stretched> |
width={52} |
||||
<Grid.Column width={1} className="user-avatar"> |
speed={2} |
||||
{this.props.postData !== null |
primaryColor="#b2e8e6" |
||||
?<Link to={"/profile/" + this.props.postData.value[1] |
secondaryColor="#00b5ad" |
||||
+ "/" + this.props.postData.value[2]} |
> |
||||
onClick={(event) => {event.stopPropagation()}}> |
<circle cx="26" cy="26" r="26" /> |
||||
{avatarView} |
</ContentLoader> |
||||
</Link> |
</div> |
||||
:avatarView |
) |
||||
} |
); |
||||
</Grid.Column> |
|
||||
<Grid.Column width={15}> |
return ( |
||||
<div className=""> |
<Transition |
||||
<div className="stretch-space-between"> |
animation="tada" |
||||
<span className={this.props.postData !== null ? "" : "grey-text"}> |
duration={500} |
||||
<strong> |
visible={this.state.animateOnToggle} |
||||
{this.props.postData !== null |
> |
||||
?this.props.postData.value[2] |
<div className="post" ref={this.postRef ? this.postRef : null}> |
||||
:"Username" |
<Divider horizontal> |
||||
|
<span className="grey-text"> |
||||
|
# |
||||
|
{this.props.postIndex} |
||||
|
</span> |
||||
|
</Divider> |
||||
|
<Grid> |
||||
|
<Grid.Row columns={16} stretched> |
||||
|
<Grid.Column width={1} className="user-avatar"> |
||||
|
{this.props.postData !== null |
||||
|
? ( |
||||
|
<Link |
||||
|
to={`/profile/${this.props.postData.value[1] |
||||
|
}/${this.props.postData.value[2]}`}
|
||||
|
onClick={(event) => { event.stopPropagation(); }} |
||||
|
> |
||||
|
{avatarView} |
||||
|
</Link> |
||||
|
) |
||||
|
: avatarView |
||||
|
} |
||||
|
</Grid.Column> |
||||
|
<Grid.Column width={15}> |
||||
|
<div className=""> |
||||
|
<div className="stretch-space-between"> |
||||
|
<span className={this.props.postData |
||||
|
!== null ? '' : 'grey-text'} |
||||
|
> |
||||
|
<strong> |
||||
|
{this.props.postData !== null |
||||
|
? this.props.postData.value[2] |
||||
|
: 'Username' |
||||
} |
} |
||||
</strong> |
</strong> |
||||
</span> |
</span> |
||||
<span className="grey-text"> |
<span className="grey-text"> |
||||
{this.props.postData !== null && |
{this.props.postData !== null |
||||
<TimeAgo date={epochTimeConverter(this.props.postData.value[3])}/> |
&& ( |
||||
|
<TimeAgo date={epochTimeConverter( |
||||
|
this.props.postData.value[3], |
||||
|
)} |
||||
|
/> |
||||
|
) |
||||
} |
} |
||||
</span> |
</span> |
||||
</div> |
</div> |
||||
<div className="stretch-space-between"> |
<div className="stretch-space-between"> |
||||
<span className={this.state.postSubject === '' ? "" : "grey-text"}> |
<span |
||||
<strong> |
className={this.state.postSubject |
||||
{this.state.postSubject === '' |
=== '' ? '' : 'grey-text'} |
||||
? <ContentLoader height={5.8} width={300} speed={2} |
> |
||||
primaryColor="#b2e8e6" secondaryColor="#00b5ad" > |
<strong> |
||||
<rect x="0" y="0" rx="3" ry="3" width="75" height="5.5" /> |
{this.state.postSubject === '' |
||||
</ContentLoader> |
? ( |
||||
: 'Subject: ' + this.state.postSubject |
<ContentLoader |
||||
|
height={5.8} |
||||
|
width={300} |
||||
|
speed={2} |
||||
|
primaryColor="#b2e8e6" |
||||
|
secondaryColor="#00b5ad" |
||||
|
> |
||||
|
<rect |
||||
|
x="0" |
||||
|
y="0" |
||||
|
rx="3" |
||||
|
ry="3" |
||||
|
width="75" |
||||
|
height="5.5" |
||||
|
/> |
||||
|
</ContentLoader> |
||||
|
) |
||||
|
: `Subject: ${ |
||||
|
this.state.postSubject}` |
||||
} |
} |
||||
</strong> |
</strong> |
||||
</span> |
</span> |
||||
</div> |
</div> |
||||
<div className="post-content"> |
<div className="post-content"> |
||||
{this.state.postContent !== '' |
{this.state.postContent !== '' |
||||
? <ReactMarkdown source={this.state.postContent} /> |
? <ReactMarkdown source={this.state.postContent} /> |
||||
: <ContentLoader height={11.2} width={300} speed={2} |
: ( |
||||
primaryColor="#b2e8e6" secondaryColor="#00b5ad" > |
<ContentLoader |
||||
<rect x="0" y="0" rx="3" ry="3" width="180" height="4.0" /> |
height={11.2} |
||||
<rect x="0" y="6.5" rx="3" ry="3" width="140" height="4.0" /> |
width={300} |
||||
</ContentLoader> |
speed={2} |
||||
} |
primaryColor="#b2e8e6" |
||||
</div> |
secondaryColor="#00b5ad" |
||||
</div> |
> |
||||
</Grid.Column> |
<rect |
||||
</Grid.Row> |
x="0" |
||||
<Grid.Row> |
y="0" |
||||
<Grid.Column floated="right" textAlign="right"> |
rx="3" |
||||
<Button icon size='mini' style={{marginRight: "0px"}}> |
ry="3" |
||||
<Icon name='chevron up' /> |
width="180" |
||||
</Button> |
height="4.0" |
||||
<Label color="teal">8000</Label> |
/> |
||||
<Button icon size='mini'> |
<rect |
||||
<Icon name='chevron down' /> |
x="0" |
||||
</Button> |
y="6.5" |
||||
<Button icon size='mini' |
rx="3" |
||||
onClick={this.props.postData |
ry="3" |
||||
? () => { this.props.navigateTo("/topic/" |
width="140" |
||||
+ this.props.postData.value[4] + "/" |
height="4.0" |
||||
+ this.props.postID)} |
/> |
||||
: () => {}}> |
</ContentLoader> |
||||
<Icon name='linkify' /> |
) |
||||
</Button> |
} |
||||
</Grid.Column> |
</div> |
||||
</Grid.Row> |
|
||||
</Grid> |
|
||||
</div> |
</div> |
||||
</Transition> |
</Grid.Column> |
||||
); |
</Grid.Row> |
||||
} |
<Grid.Row> |
||||
|
<Grid.Column floated="right" textAlign="right"> |
||||
|
<Button |
||||
|
icon |
||||
|
size="mini" |
||||
|
style={{ |
||||
|
marginRight: '0px' |
||||
|
}} |
||||
|
> |
||||
|
<Icon name="chevron up" /> |
||||
|
</Button> |
||||
|
<Label color="teal">8000</Label> |
||||
|
<Button icon size="mini"> |
||||
|
<Icon name="chevron down" /> |
||||
|
</Button> |
||||
|
<Button |
||||
|
icon |
||||
|
size="mini" |
||||
|
onClick={this.props.postData |
||||
|
? () => { |
||||
|
this.props.navigateTo(`/topic/${ |
||||
|
this.props.postData.value[4]}/${ |
||||
|
this.props.postID}`);
|
||||
|
} |
||||
|
: () => {}} |
||||
|
> |
||||
|
<Icon name="linkify" /> |
||||
|
</Button> |
||||
|
</Grid.Column> |
||||
|
</Grid.Row> |
||||
|
</Grid> |
||||
|
</div> |
||||
|
</Transition> |
||||
|
); |
||||
|
} |
||||
|
|
||||
componentDidMount() { |
componentDidMount() { |
||||
this.getBlockchainData(); |
this.getBlockchainData(); |
||||
} |
} |
||||
|
|
||||
componentDidUpdate(){ |
componentDidUpdate() { |
||||
this.getBlockchainData(); |
this.getBlockchainData(); |
||||
if (this.state.readyForAnimation){ |
if (this.state.readyForAnimation) { |
||||
if (this.postRef){ |
if (this.postRef) { |
||||
setTimeout(() => { |
setTimeout(() => { |
||||
this.postRef.current.scrollIntoView({ block: 'start', behavior: 'smooth' }); |
this.postRef.current.scrollIntoView( |
||||
setTimeout(() => { |
{ |
||||
this.setState({ animateOnToggle: false }); |
block: 'start', behavior: 'smooth' |
||||
}, 300); |
}, |
||||
}, 100); |
); |
||||
this.setState({ readyForAnimation: false }); |
setTimeout(() => { |
||||
} |
this.setState({ |
||||
} |
animateOnToggle: false |
||||
|
}); |
||||
|
}, 300); |
||||
|
}, 100); |
||||
|
this.setState({ |
||||
|
readyForAnimation: false |
||||
|
}); |
||||
|
} |
||||
} |
} |
||||
}; |
} |
||||
|
} |
||||
|
|
||||
const mapDispatchToProps = dispatch => bindActionCreators({ |
const mapDispatchToProps = dispatch => bindActionCreators({ |
||||
navigateTo: (location) => push(location) |
navigateTo: location => push(location) |
||||
}, dispatch); |
}, dispatch); |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
user: state.user, |
||||
user: state.user, |
orbitDB: state.orbit |
||||
orbitDB: state.orbit |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post)); |
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post)); |
@ -1,132 +1,144 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
import { drizzle } from '../index'; |
import { drizzle } from '../index'; |
||||
|
|
||||
import UserAvatar from 'react-user-avatar'; |
|
||||
import epochTimeConverter from '../helpers/EpochTimeConverter'; |
import epochTimeConverter from '../helpers/EpochTimeConverter'; |
||||
|
|
||||
import UsernameFormContainer from '../containers/UsernameFormContainer'; |
import UsernameFormContainer from '../containers/UsernameFormContainer'; |
||||
|
|
||||
const callsInfo = [{ |
const callsInfo = [ |
||||
contract: 'Forum', |
{ |
||||
method: 'getUserDateOfRegister' |
contract: 'Forum', |
||||
},{ |
method: 'getUserDateOfRegister' |
||||
contract: 'Forum', |
}, { |
||||
method: 'getOrbitDBId' |
contract: 'Forum', |
||||
}] |
method: 'getOrbitDBId' |
||||
|
}]; |
||||
|
|
||||
class ProfileInformation extends Component { |
class ProfileInformation extends Component { |
||||
constructor(props) { |
constructor(props) { |
||||
super(props); |
super(props); |
||||
|
|
||||
this.getBlockchainData = this.getBlockchainData.bind(this); |
this.getBlockchainData = this.getBlockchainData.bind(this); |
||||
this.dataKey = []; |
this.dataKey = []; |
||||
|
|
||||
this.state = { |
this.state = { |
||||
pageStatus: 'initialized', |
pageStatus: 'initialized', |
||||
dateOfRegister: '', |
dateOfRegister: '', |
||||
orbitDBId: '' |
orbitDBId: '' |
||||
}; |
}; |
||||
|
} |
||||
|
|
||||
|
getBlockchainData() { |
||||
|
if (this.state.pageStatus === 'initialized' |
||||
|
&& this.props.drizzleStatus.initialized) { |
||||
|
callsInfo.forEach((call, index) => { |
||||
|
this.dataKey[index] = drizzle.contracts[call.contract].methods[call.method].cacheCall( |
||||
|
this.props.address, |
||||
|
); |
||||
|
}); |
||||
|
this.setState({ |
||||
|
pageStatus: 'loading' |
||||
|
}); |
||||
} |
} |
||||
|
|
||||
getBlockchainData(){ |
if (this.state.pageStatus === 'loading') { |
||||
if (this.state.pageStatus === 'initialized' && |
let pageStatus = 'loaded'; |
||||
this.props.drizzleStatus['initialized']) { |
callsInfo.forEach((call, index) => { |
||||
callsInfo.forEach((call, index) => { |
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) { |
||||
this.dataKey[index] = drizzle.contracts[call.contract] |
pageStatus = 'loading'; |
||||
.methods[call.method].cacheCall(this.props.address); |
|
||||
}) |
|
||||
this.setState({ pageStatus: 'loading' }); |
|
||||
} |
} |
||||
|
}); |
||||
|
|
||||
if (this.state.pageStatus === 'loading') { |
if (pageStatus === 'loaded') { |
||||
var pageStatus = 'loaded'; |
this.setState({ |
||||
callsInfo.forEach((call, index) => { |
pageStatus |
||||
if (!this.props.contracts[call.contract][call.method][this.dataKey[index]]) { |
}); |
||||
pageStatus = 'loading'; |
} |
||||
return; |
} |
||||
} |
|
||||
}) |
|
||||
|
|
||||
if (pageStatus === 'loaded') { |
if (this.state.pageStatus === 'loaded') { |
||||
this.setState({ pageStatus: pageStatus }); |
if (this.state.dateOfRegister === '') { |
||||
} |
const transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; |
||||
|
if (transaction) { |
||||
|
this.setState({ |
||||
|
dateOfRegister: transaction.value |
||||
|
}); |
||||
} |
} |
||||
|
} |
||||
if (this.state.pageStatus === 'loaded'){ |
if (this.state.orbitDBId === '') { |
||||
if (this.state.dateOfRegister === ''){ |
const transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; |
||||
let transaction = this.props.contracts[callsInfo[0].contract][callsInfo[0].method][this.dataKey[0]]; |
if (transaction) { |
||||
if (transaction){ |
this.setState({ |
||||
this.setState({ dateOfRegister: transaction.value }); |
orbitDBId: transaction.value |
||||
} |
}); |
||||
} |
|
||||
if (this.state.orbitDBId === ''){ |
|
||||
let transaction = this.props.contracts[callsInfo[1].contract][callsInfo[1].method][this.dataKey[1]]; |
|
||||
if (transaction){ |
|
||||
this.setState({ orbitDBId: transaction.value }); |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
|
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
render() { |
render() { |
||||
return ( |
return ( |
||||
<div className="user-info"> |
<div className="user-info"> |
||||
{this.props.avatarUrl && <UserAvatar |
{this.props.avatarUrl && ( |
||||
size="40" |
<UserAvatar |
||||
className="inline user-avatar" |
size="40" |
||||
src={this.props.avatarUrl} |
className="inline user-avatar" |
||||
name={this.props.username}/>} |
src={this.props.avatarUrl} |
||||
<table className="highlight centered responsive-table"> |
name={this.props.username} |
||||
<tbody> |
/> |
||||
<tr> |
)} |
||||
<td><strong>Username:</strong></td> |
<table className="highlight centered responsive-table"> |
||||
<td>{this.props.username}</td> |
<tbody> |
||||
</tr> |
<tr> |
||||
<tr> |
<td><strong>Username:</strong></td> |
||||
<td><strong>Account address:</strong></td> |
<td>{this.props.username}</td> |
||||
<td>{this.props.address}</td> |
</tr> |
||||
</tr> |
<tr> |
||||
<tr> |
<td><strong>Account address:</strong></td> |
||||
<td><strong>OrbitDB:</strong></td> |
<td>{this.props.address}</td> |
||||
<td>{this.state.orbitDBId}</td> |
</tr> |
||||
</tr> |
<tr> |
||||
<tr> |
<td><strong>OrbitDB:</strong></td> |
||||
<td><strong>Number of topics created:</strong></td> |
<td>{this.state.orbitDBId}</td> |
||||
<td>{this.props.numberOfTopics}</td> |
</tr> |
||||
</tr> |
<tr> |
||||
<tr> |
<td><strong>Number of topics created:</strong></td> |
||||
<td><strong>Number of posts:</strong></td> |
<td>{this.props.numberOfTopics}</td> |
||||
<td>{this.props.numberOfPosts}</td> |
</tr> |
||||
</tr> |
<tr> |
||||
{this.state.dateOfRegister && |
<td><strong>Number of posts:</strong></td> |
||||
<tr> |
<td>{this.props.numberOfPosts}</td> |
||||
<td><strong>Member since:</strong></td> |
</tr> |
||||
<td>{epochTimeConverter(this.state.dateOfRegister)}</td> |
{this.state.dateOfRegister |
||||
</tr> |
&& ( |
||||
} |
<tr> |
||||
</tbody> |
<td><strong>Member since:</strong></td> |
||||
</table> |
<td>{epochTimeConverter(this.state.dateOfRegister)}</td> |
||||
{this.props.self && <UsernameFormContainer/>} |
</tr> |
||||
</div> |
) |
||||
); |
} |
||||
} |
</tbody> |
||||
|
</table> |
||||
|
{this.props.self && <UsernameFormContainer />} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
componentDidMount() { |
componentDidMount() { |
||||
this.getBlockchainData(); |
this.getBlockchainData(); |
||||
} |
} |
||||
|
|
||||
componentDidUpdate(){ |
componentDidUpdate() { |
||||
this.getBlockchainData(); |
this.getBlockchainData(); |
||||
} |
} |
||||
}; |
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
drizzleStatus: state.drizzleStatus, |
||||
drizzleStatus: state.drizzleStatus, |
contracts: state.contracts, |
||||
contracts: state.contracts, |
user: state.user |
||||
user: state.user |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(ProfileInformation); |
export default connect(mapStateToProps)(ProfileInformation); |
||||
|
@ -1,118 +1,139 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
import { withRouter } from 'react-router' |
import { withRouter } from 'react-router-dom'; |
||||
|
|
||||
import ContentLoader from "react-content-loader" |
import ContentLoader from 'react-content-loader'; |
||||
import { Card } from 'semantic-ui-react' |
import { Card } from 'semantic-ui-react'; |
||||
|
|
||||
import TimeAgo from 'react-timeago'; |
import TimeAgo from 'react-timeago'; |
||||
import epochTimeConverter from '../helpers/EpochTimeConverter' |
import epochTimeConverter from '../helpers/EpochTimeConverter'; |
||||
|
|
||||
class Topic extends Component { |
class Topic extends Component { |
||||
constructor(props){ |
constructor(props) { |
||||
super(props); |
super(props); |
||||
|
|
||||
this.fetchSubject = this.fetchSubject.bind(this); |
this.fetchSubject = this.fetchSubject.bind(this); |
||||
|
|
||||
this.state = { |
this.state = { |
||||
topicSubject: null, |
topicSubject: null, |
||||
topicSubjectFetchStatus: 'pending' |
topicSubjectFetchStatus: 'pending' |
||||
} |
}; |
||||
} |
} |
||||
|
|
||||
async fetchSubject(topicID) { |
|
||||
var topicSubject; |
|
||||
|
|
||||
if (this.props.topicData.value[1] === this.props.user.address) { |
async fetchSubject(topicID) { |
||||
let orbitData = this.props.orbitDB.topicsDB.get(topicID); |
let topicSubject; |
||||
topicSubject = orbitData['subject'] |
|
||||
} else { |
|
||||
const fullAddress = "/orbitdb/" + this.props.topicData.value[0] + "/topics"; |
|
||||
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
|
||||
await store.load(); |
|
||||
|
|
||||
let localOrbitData = store.get(topicID); |
if (this.props.topicData.value[1] === this.props.user.address) { |
||||
if (localOrbitData) { |
const orbitData = this.props.orbitDB.topicsDB.get(topicID); |
||||
topicSubject = localOrbitData['subject']; |
topicSubject = orbitData.subject; |
||||
} else { |
} else { |
||||
// Wait until we have received something from the network
|
const fullAddress = `/orbitdb/${this.props.topicData.value[0] |
||||
store.events.on('replicated', () => { |
}/topics`;
|
||||
topicSubject = store.get(topicID)['subject']; |
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
||||
}) |
await store.load(); |
||||
} |
|
||||
} |
|
||||
|
|
||||
this.setState({ |
const localOrbitData = store.get(topicID); |
||||
topicSubject: topicSubject, |
if (localOrbitData) { |
||||
topicSubjectFetchStatus: 'fetched' |
topicSubject = localOrbitData.subject; |
||||
}) |
} else { |
||||
|
// Wait until we have received something from the network
|
||||
|
store.events.on('replicated', () => { |
||||
|
topicSubject = store.get(topicID).subject; |
||||
|
}); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
render(){ |
this.setState({ |
||||
return ( |
topicSubject, |
||||
<Card link className="card" |
topicSubjectFetchStatus: 'fetched' |
||||
onClick={() => {this.props.history.push("/topic/" + this.props.topicID)}}> |
}); |
||||
<Card.Content> |
} |
||||
<div className={"topic-subject" + (this.state.topicSubject ? "" : " grey-text")}> |
|
||||
<p><strong> |
|
||||
{this.state.topicSubject !== null ? this.state.topicSubject |
|
||||
:<ContentLoader height={5.8} width={300} speed={2} |
|
||||
primaryColor="#b2e8e6" secondaryColor="#00b5ad" > |
|
||||
<rect x="0" y="0" rx="3" ry="3" width="150" height="5.5" /> |
|
||||
</ContentLoader>} |
|
||||
</strong></p> |
|
||||
</div> |
|
||||
<hr/> |
|
||||
<div className="topic-meta"> |
|
||||
<p className={"no-margin" + |
|
||||
(this.props.topicData !== null ? "" : " grey-text")}> |
|
||||
{this.props.topicData !== null |
|
||||
?this.props.topicData.value[2] |
|
||||
:"Username" |
|
||||
} |
|
||||
</p> |
|
||||
<p className={"no-margin" + |
|
||||
(this.props.topicData !== null ? "" : " grey-text")}> |
|
||||
{"Number of replies: " + (this.props.topicData !== null |
|
||||
?this.props.topicData.value[4].length |
|
||||
:"") |
|
||||
} |
|
||||
</p> |
|
||||
<p className="topic-date grey-text"> |
|
||||
{this.props.topicData !== null && |
|
||||
<TimeAgo date={epochTimeConverter(this.props.topicData.value[3])}/> |
|
||||
} |
|
||||
</p> |
|
||||
</div> |
|
||||
</Card.Content> |
|
||||
</Card> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
componentDidMount() { |
render() { |
||||
if (this.props.topicData !== null && |
return ( |
||||
this.state.topicSubjectFetchStatus === "pending" && |
<Card |
||||
this.props.orbitDB.ipfsInitialized && |
link |
||||
this.props.orbitDB.orbitdb) { |
className="card" |
||||
this.fetchSubject(this.props.topicID); |
onClick={() => { |
||||
} |
this.props.history.push(`/topic/${this.props.topicID}`); |
||||
} |
}} |
||||
|
> |
||||
|
<Card.Content> |
||||
|
<div className={`topic-subject${ |
||||
|
this.state.topicSubject ? '' : ' grey-text'}`}
|
||||
|
> |
||||
|
<p> |
||||
|
<strong> |
||||
|
{this.state.topicSubject !== null ? this.state.topicSubject |
||||
|
: ( |
||||
|
<ContentLoader |
||||
|
height={5.8} |
||||
|
width={300} |
||||
|
speed={2} |
||||
|
primaryColor="#b2e8e6" |
||||
|
secondaryColor="#00b5ad" |
||||
|
> |
||||
|
<rect x="0" y="0" rx="3" ry="3" width="150" height="5.5" /> |
||||
|
</ContentLoader> |
||||
|
)} |
||||
|
</strong> |
||||
|
</p> |
||||
|
</div> |
||||
|
<hr /> |
||||
|
<div className="topic-meta"> |
||||
|
<p className={`no-margin${ |
||||
|
this.props.topicData !== null ? '' : ' grey-text'}`}
|
||||
|
> |
||||
|
{this.props.topicData !== null |
||||
|
? this.props.topicData.value[2] |
||||
|
: 'Username' |
||||
|
} |
||||
|
</p> |
||||
|
<p className={`no-margin${ |
||||
|
this.props.topicData !== null ? '' : ' grey-text'}`}
|
||||
|
> |
||||
|
{`Number of replies: ${this.props.topicData !== null |
||||
|
? this.props.topicData.value[4].length |
||||
|
: ''}` |
||||
|
} |
||||
|
</p> |
||||
|
<p className="topic-date grey-text"> |
||||
|
{this.props.topicData !== null |
||||
|
&& ( |
||||
|
<TimeAgo |
||||
|
date={epochTimeConverter(this.props.topicData.value[3])} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
</p> |
||||
|
</div> |
||||
|
</Card.Content> |
||||
|
</Card> |
||||
|
); |
||||
|
} |
||||
|
|
||||
componentDidUpdate() { |
componentDidMount() { |
||||
if (this.props.topicData !== null && |
if (this.props.topicData !== null |
||||
this.state.topicSubjectFetchStatus === "pending" && |
&& this.state.topicSubjectFetchStatus === 'pending' |
||||
this.props.orbitDB.ipfsInitialized && |
&& this.props.orbitDB.ipfsInitialized |
||||
this.props.orbitDB.orbitdb) { |
&& this.props.orbitDB.orbitdb) { |
||||
this.fetchSubject(this.props.topicID); |
this.fetchSubject(this.props.topicID); |
||||
} |
|
||||
} |
} |
||||
}; |
} |
||||
|
|
||||
const mapStateToProps = state => { |
componentDidUpdate() { |
||||
return { |
if (this.props.topicData !== null |
||||
user: state.user, |
&& this.state.topicSubjectFetchStatus === 'pending' |
||||
orbitDB: state.orbit |
&& this.props.orbitDB.ipfsInitialized |
||||
|
&& this.props.orbitDB.orbitdb) { |
||||
|
this.fetchSubject(this.props.topicID); |
||||
} |
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
|
const mapStateToProps = state => ({ |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbit |
||||
|
}); |
||||
|
|
||||
export default withRouter(connect(mapStateToProps)(Topic)); |
export default withRouter(connect(mapStateToProps)(Topic)); |
||||
|
@ -1,20 +1,20 @@ |
|||||
import Forum from "../contracts/Forum.json"; |
import Forum from '../contracts/Forum.json'; |
||||
|
|
||||
const drizzleOptions = { |
const drizzleOptions = { |
||||
web3: { |
web3: { |
||||
fallback: { |
fallback: { |
||||
type: 'ws', |
type: 'ws', |
||||
url: 'ws://127.0.0.1:9545' |
url: 'ws://127.0.0.1:9545' |
||||
} |
} |
||||
}, |
}, |
||||
contracts: [Forum], |
contracts: [Forum], |
||||
events: { |
events: { |
||||
Forum: ['UserSignedUp', 'UsernameUpdated', 'TopicCreated', 'PostCreated'] |
Forum: ['UserSignedUp', 'UsernameUpdated', 'TopicCreated', 'PostCreated'] |
||||
}, |
}, |
||||
polls: { |
polls: { |
||||
accounts: 2000, |
accounts: 2000, |
||||
blocks: 2000 |
blocks: 2000 |
||||
}, |
} |
||||
}; |
}; |
||||
|
|
||||
export default drizzleOptions; |
export default drizzleOptions; |
@ -1,17 +1,19 @@ |
|||||
// OrbitDB uses Pubsub which is an experimental feature
|
// OrbitDB uses Pubsub which is an experimental feature
|
||||
// and need to be turned on manually.
|
// and need to be turned on manually.
|
||||
const ipfsOptions = { |
const ipfsOptions = { |
||||
EXPERIMENTAL: { |
EXPERIMENTAL: { |
||||
pubsub: true |
pubsub: true |
||||
}, config: { |
}, |
||||
Addresses: { |
config: { |
||||
Swarm: [ |
Addresses: { |
||||
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star', |
Swarm: [ |
||||
// Use local signal server (https://github.com/libp2p/js-libp2p-websocket-star-rendezvous)
|
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star', |
||||
'/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star' |
// Use local signal server (https://github.com/libp2p/js-libp2p-websocket-star-rendezvous)
|
||||
] |
// (e.g. rendezvous --port=9090 --host=127.0.0.1)
|
||||
} |
'/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star' |
||||
|
] |
||||
} |
} |
||||
|
} |
||||
}; |
}; |
||||
|
|
||||
export default ipfsOptions; |
export default ipfsOptions; |
@ -1,117 +1,124 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
import { drizzle } from '../index'; |
import { withRouter } from 'react-router-dom'; |
||||
import { withRouter } from 'react-router-dom' |
|
||||
|
|
||||
import { Header } from 'semantic-ui-react'; |
import { Header } from 'semantic-ui-react'; |
||||
|
import { drizzle } from '../index'; |
||||
|
|
||||
import TopicList from '../components/TopicList'; |
import TopicList from '../components/TopicList'; |
||||
import FloatingButton from '../components/FloatingButton'; |
import FloatingButton from '../components/FloatingButton'; |
||||
|
|
||||
/*import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions';*/ |
/* import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions'; */ |
||||
|
|
||||
const contract = "Forum"; |
const contract = 'Forum'; |
||||
const getNumberOfTopicsMethod = "getNumberOfTopics"; |
const getNumberOfTopicsMethod = 'getNumberOfTopics'; |
||||
|
|
||||
class BoardContainer extends Component { |
class BoardContainer extends Component { |
||||
constructor(props) { |
constructor(props) { |
||||
super(props); |
super(props); |
||||
|
|
||||
/*this.props.store.dispatch(showProgressBar());*/ |
/* this.props.store.dispatch(showProgressBar()); */ |
||||
|
|
||||
this.getBlockchainData = this.getBlockchainData.bind(this); |
this.getBlockchainData = this.getBlockchainData.bind(this); |
||||
this.handleCreateTopicClick = this.handleCreateTopicClick.bind(this); |
this.handleCreateTopicClick = this.handleCreateTopicClick.bind(this); |
||||
|
|
||||
this.state = { |
this.state = { |
||||
pageStatus: 'initialized' |
pageStatus: 'initialized' |
||||
} |
}; |
||||
|
} |
||||
|
|
||||
|
getBlockchainData() { |
||||
|
if (this.state.pageStatus === 'initialized' |
||||
|
&& this.props.drizzleStatus.initialized) { |
||||
|
this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall(); |
||||
|
this.setState({ |
||||
|
pageStatus: 'loading' |
||||
|
}); |
||||
} |
} |
||||
|
if (this.state.pageStatus === 'loading' |
||||
getBlockchainData() { |
&& this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]) { |
||||
if (this.state.pageStatus === 'initialized' && |
this.setState({ |
||||
this.props.drizzleStatus['initialized']){ |
pageStatus: 'loaded' |
||||
this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall(); |
}); |
||||
this.setState({ pageStatus: 'loading' }); |
/* this.props.store.dispatch(hideProgressBar()); */ |
||||
} |
|
||||
if (this.state.pageStatus === 'loading' && |
|
||||
this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]){ |
|
||||
this.setState({ pageStatus: 'loaded' }); |
|
||||
/*this.props.store.dispatch(hideProgressBar());*/ |
|
||||
} |
|
||||
} |
} |
||||
|
} |
||||
|
|
||||
handleCreateTopicClick() { |
handleCreateTopicClick() { |
||||
this.props.history.push("/startTopic"); |
this.props.history.push('/startTopic'); |
||||
} |
} |
||||
|
|
||||
render() { |
render() { |
||||
var boardContents; |
let boardContents; |
||||
if (this.state.pageStatus === 'loaded'){ |
if (this.state.pageStatus === 'loaded') { |
||||
var numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; |
const numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; |
||||
|
|
||||
if (numberOfTopics !== '0'){ |
|
||||
this.topicIDs = []; |
|
||||
for (var i = 0; i < numberOfTopics; i++) { |
|
||||
this.topicIDs.push(i); |
|
||||
} |
|
||||
boardContents = ([ |
|
||||
<TopicList topicIDs={this.topicIDs} key="topicList"/>, |
|
||||
<div className="bottom-overlay-pad" key="pad"></div>, |
|
||||
this.props.hasSignedUp && |
|
||||
<FloatingButton onClick={this.handleCreateTopicClick} |
|
||||
key="createTopicButton"/> |
|
||||
]); |
|
||||
} else { |
|
||||
if (!this.props.hasSignedUp){ |
|
||||
boardContents = ( |
|
||||
<div className="vertical-center-in-parent"> |
|
||||
<Header color='teal' textAlign='center' as='h2'> |
|
||||
There are no topics yet! |
|
||||
</Header> |
|
||||
<Header color='teal' textAlign='center' as='h4'> |
|
||||
Sign up to be the first to post. |
|
||||
</Header> |
|
||||
</div> |
|
||||
); |
|
||||
} else { |
|
||||
boardContents = ( |
|
||||
<div className="vertical-center-in-parent"> |
|
||||
<Header color='teal' textAlign='center' as='h2'> |
|
||||
There are no topics yet! |
|
||||
</Header> |
|
||||
<Header color='teal' textAlign='center' as='h4'> |
|
||||
Click the add button at the bottom of the page to be the first to post. |
|
||||
</Header> |
|
||||
<FloatingButton onClick={this.handleCreateTopicClick} |
|
||||
key="createTopicButton"/> |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ( |
if (numberOfTopics !== '0') { |
||||
<div className="fill"> |
this.topicIDs = []; |
||||
{boardContents} |
for (let i = 0; i < numberOfTopics; i++) { |
||||
</div> |
this.topicIDs.push(i); |
||||
|
} |
||||
|
boardContents = ([ |
||||
|
<TopicList topicIDs={this.topicIDs} key="topicList" />, |
||||
|
<div className="bottom-overlay-pad" key="pad" />, |
||||
|
this.props.hasSignedUp |
||||
|
&& ( |
||||
|
<FloatingButton |
||||
|
onClick={this.handleCreateTopicClick} |
||||
|
key="createTopicButton" |
||||
|
/> |
||||
|
) |
||||
|
]); |
||||
|
} else if (!this.props.hasSignedUp) { |
||||
|
boardContents = ( |
||||
|
<div className="vertical-center-in-parent"> |
||||
|
<Header color="teal" textAlign="center" as="h2"> |
||||
|
There are no topics yet! |
||||
|
</Header> |
||||
|
<Header color="teal" textAlign="center" as="h4"> |
||||
|
Sign up to be the first to post. |
||||
|
</Header> |
||||
|
</div> |
||||
|
); |
||||
|
} else { |
||||
|
boardContents = ( |
||||
|
<div className="vertical-center-in-parent"> |
||||
|
<Header color="teal" textAlign="center" as="h2"> |
||||
|
There are no topics yet! |
||||
|
</Header> |
||||
|
<Header color="teal" textAlign="center" as="h4"> |
||||
|
Click the add button at the bottom of the page to be the first |
||||
|
to post. |
||||
|
</Header> |
||||
|
<FloatingButton |
||||
|
onClick={this.handleCreateTopicClick} |
||||
|
key="createTopicButton" |
||||
|
/> |
||||
|
</div> |
||||
); |
); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
componentDidMount() { |
return ( |
||||
this.getBlockchainData(); |
<div className="fill"> |
||||
} |
{boardContents} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
componentDidUpdate(){ |
componentDidMount() { |
||||
this.getBlockchainData(); |
this.getBlockchainData(); |
||||
} |
} |
||||
|
|
||||
|
componentDidUpdate() { |
||||
|
this.getBlockchainData(); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
contracts: state.contracts, |
||||
contracts: state.contracts, |
drizzleStatus: state.drizzleStatus, |
||||
drizzleStatus: state.drizzleStatus, |
hasSignedUp: state.user.hasSignedUp |
||||
hasSignedUp: state.user.hasSignedUp |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default withRouter(connect(mapStateToProps)(BoardContainer)); |
export default withRouter(connect(mapStateToProps)(BoardContainer)); |
||||
|
@ -1,53 +1,63 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { bindActionCreators } from 'redux'; |
import { bindActionCreators } from 'redux'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
import { push } from 'connected-react-router' |
import { push } from 'connected-react-router'; |
||||
import { Image, Menu } from 'semantic-ui-react' |
import { Image, Menu } from 'semantic-ui-react'; |
||||
|
|
||||
import logo from '../assets/images/logo.png'; |
import logo from '../assets/images/logo.png'; |
||||
|
|
||||
class NavBarContainer extends Component { |
class NavBarContainer extends Component { |
||||
render() { |
render() { |
||||
return ( |
return ( |
||||
<Menu fixed='top' inverted> |
<Menu fixed="top" inverted> |
||||
<Menu.Item header onClick={() => {this.props.navigateTo('/')}}> |
<Menu.Item header onClick={() => { this.props.navigateTo('/'); }}> |
||||
<Image |
<Image |
||||
size='mini' |
size="mini" |
||||
src={logo} |
src={logo} |
||||
style={{ marginRight: '1.5em' }} |
style={{ |
||||
/> |
marginRight: '1.5em' |
||||
Apella |
}} |
||||
</Menu.Item> |
/> |
||||
<Menu.Item onClick={() => {this.props.navigateTo('/home')}}> |
Apella |
||||
Home |
</Menu.Item> |
||||
</Menu.Item> |
<Menu.Item onClick={() => { this.props.navigateTo('/home'); }}> |
||||
{this.props.hasSignedUp |
Home |
||||
?<Menu.Item onClick={() => {this.props.navigateTo('/profile')}}> |
</Menu.Item> |
||||
Profile |
{this.props.hasSignedUp |
||||
</Menu.Item> |
? ( |
||||
:<Menu.Menu position='right' style={{backgroundColor: '#00b5ad'}}> |
<Menu.Item onClick={() => { this.props.navigateTo('/profile'); }}> |
||||
<Menu.Item onClick={() => {this.props.navigateTo('/signup')}}> |
Profile |
||||
SignUp |
</Menu.Item> |
||||
</Menu.Item> |
) |
||||
</Menu.Menu> |
: ( |
||||
} |
<Menu.Menu |
||||
<div className="navBarText"> |
position="right" |
||||
{this.props.navBarTitle !== '' && <span>{this.props.navBarTitle}</span>} |
style={{ |
||||
</div> |
backgroundColor: '#00b5ad' |
||||
</Menu> |
}} |
||||
); |
> |
||||
} |
<Menu.Item onClick={() => { this.props.navigateTo('/signup'); }}> |
||||
|
SignUp |
||||
|
</Menu.Item> |
||||
|
</Menu.Menu> |
||||
|
) |
||||
|
} |
||||
|
<div className="navBarText"> |
||||
|
{this.props.navBarTitle !== '' |
||||
|
&& <span>{this.props.navBarTitle}</span>} |
||||
|
</div> |
||||
|
</Menu> |
||||
|
); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
const mapDispatchToProps = dispatch => bindActionCreators({ |
const mapDispatchToProps = dispatch => bindActionCreators({ |
||||
navigateTo: (location) => push(location) |
navigateTo: location => push(location) |
||||
}, dispatch); |
}, dispatch); |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
hasSignedUp: state.user.hasSignedUp, |
||||
hasSignedUp: state.user.hasSignedUp, |
navBarTitle: state.interface.navBarTitle |
||||
navBarTitle: state.interface.navBarTitle |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NavBarContainer); |
export default connect(mapStateToProps, mapDispatchToProps)(NavBarContainer); |
||||
|
@ -1,43 +1,46 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
|
|
||||
import { Header } from 'semantic-ui-react'; |
import { Header } from 'semantic-ui-react'; |
||||
import {connect} from "react-redux"; |
import { connect } from 'react-redux'; |
||||
import UsernameFormContainer from './UsernameFormContainer'; |
import UsernameFormContainer from './UsernameFormContainer'; |
||||
|
|
||||
class SignUpContainer extends Component { |
class SignUpContainer extends Component { |
||||
componentDidUpdate(prevProps) { |
componentDidUpdate(prevProps) { |
||||
if (this.props.user.hasSignedUp && !prevProps.user.hasSignedUp) |
if (this.props.user.hasSignedUp && !prevProps.user.hasSignedUp) this.props.history.push('/'); |
||||
this.props.history.push("/"); |
} |
||||
} |
|
||||
|
|
||||
render() { |
render() { |
||||
return ( |
return ( |
||||
this.props.user.hasSignedUp |
this.props.user.hasSignedUp |
||||
?(<div className="vertical-center-in-parent"> |
? ( |
||||
<Header color='teal' textAlign='center' as='h2'> |
<div className="vertical-center-in-parent"> |
||||
There is already an account for this addresss. |
<Header color="teal" textAlign="center" as="h2"> |
||||
</Header> |
There is already an account for this addresss. |
||||
<Header color='teal' textAlign='center' as='h4'> |
</Header> |
||||
If you want to create another account please change your address. |
<Header color="teal" textAlign="center" as="h4"> |
||||
</Header> |
If you want to create another account please change your address. |
||||
</div>) |
</Header> |
||||
:(<div className="sign-up-container"> |
</div> |
||||
<div> |
) |
||||
<h1>Sign Up</h1> |
: ( |
||||
<p className="no-margin"> |
<div className="sign-up-container"> |
||||
<strong>Account address:</strong> {this.props.user.address} |
<div> |
||||
</p> |
<h1>Sign Up</h1> |
||||
<UsernameFormContainer /> |
<p className="no-margin"> |
||||
</div> |
<strong>Account address:</strong> |
||||
</div>) |
{' '} |
||||
); |
{this.props.user.address} |
||||
} |
</p> |
||||
|
<UsernameFormContainer /> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
user: state.user |
||||
user: state.user |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(SignUpContainer); |
export default connect(mapStateToProps)(SignUpContainer); |
||||
|
@ -1,132 +1,151 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
|
|
||||
import { Form, TextArea, Button, Icon } from 'semantic-ui-react' |
import { Button, Form, Icon, TextArea } from 'semantic-ui-react'; |
||||
import NewTopicPreview from '../components/NewTopicPreview' |
import NewTopicPreview from '../components/NewTopicPreview'; |
||||
|
|
||||
import { createTopic } from '../redux/actions/transactionsActions'; |
import { createTopic } from '../redux/actions/transactionsActions'; |
||||
|
|
||||
class StartTopicContainer extends Component { |
class StartTopicContainer extends Component { |
||||
constructor(props, context) { |
constructor(props, context) { |
||||
super(props); |
super(props); |
||||
|
|
||||
this.handleInputChange = this.handleInputChange.bind(this); |
this.handleInputChange = this.handleInputChange.bind(this); |
||||
this.handlePreviewToggle = this.handlePreviewToggle.bind(this); |
this.handlePreviewToggle = this.handlePreviewToggle.bind(this); |
||||
this.validateAndPost = this.validateAndPost.bind(this); |
this.validateAndPost = this.validateAndPost.bind(this); |
||||
|
|
||||
this.state = { |
this.state = { |
||||
topicSubjectInput: '', |
topicSubjectInput: '', |
||||
topicMessageInput: '', |
topicMessageInput: '', |
||||
topicSubjectInputEmptySubmit: false, |
topicSubjectInputEmptySubmit: false, |
||||
topicMessageInputEmptySubmit: false, |
topicMessageInputEmptySubmit: false, |
||||
previewEnabled: false, |
previewEnabled: false, |
||||
previewDate: "" |
previewDate: '' |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
async validateAndPost() { |
|
||||
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput === ''){ |
|
||||
this.setState({ |
|
||||
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', |
|
||||
topicMessageInputEmptySubmit: this.state.topicMessageInput === '' |
|
||||
}); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.props.dispatch( |
async validateAndPost() { |
||||
createTopic( |
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput |
||||
{ |
=== '') { |
||||
topicSubject: this.state.topicSubjectInput, |
this.setState({ |
||||
topicMessage: this.state.topicMessageInput |
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', |
||||
} |
topicMessageInputEmptySubmit: this.state.topicMessageInput === '' |
||||
) |
}); |
||||
); |
return; |
||||
this.props.history.push("/home"); |
|
||||
} |
} |
||||
|
|
||||
handleInputChange(event) { |
this.props.dispatch( |
||||
this.setState({[event.target.name]: event.target.value}); |
createTopic( |
||||
} |
{ |
||||
|
topicSubject: this.state.topicSubjectInput, |
||||
|
topicMessage: this.state.topicMessageInput |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
this.props.history.push('/home'); |
||||
|
} |
||||
|
|
||||
handlePreviewToggle() { |
handleInputChange(event) { |
||||
this.setState((prevState, props) => ({ |
this.setState({ |
||||
previewEnabled: !prevState.previewEnabled, |
[event.target.name]: event.target.value |
||||
previewDate: this.getDate() |
}); |
||||
})); |
} |
||||
} |
|
||||
|
|
||||
getDate() { |
handlePreviewToggle() { |
||||
const currentdate = new Date(); |
this.setState((prevState, props) => ({ |
||||
return ((currentdate.getMonth() + 1) + " " |
previewEnabled: !prevState.previewEnabled, |
||||
+ currentdate.getDate() + ", " |
previewDate: this.getDate() |
||||
+ currentdate.getFullYear() + ", " |
})); |
||||
+ currentdate.getHours() + ":" |
} |
||||
+ currentdate.getMinutes() + ":" |
|
||||
+ currentdate.getSeconds()); |
|
||||
} |
|
||||
|
|
||||
render() { |
getDate() { |
||||
if (!this.props.user.hasSignedUp) { |
const currentdate = new Date(); |
||||
this.props.history.push("/signup"); |
return (`${currentdate.getMonth() + 1} ${ |
||||
return(null); |
currentdate.getDate()}, ${ |
||||
} |
currentdate.getFullYear()}, ${ |
||||
|
currentdate.getHours()}:${ |
||||
|
currentdate.getMinutes()}:${ |
||||
|
currentdate.getSeconds()}`);
|
||||
|
} |
||||
|
|
||||
var previewEditText = this.state.previewEnabled ? "Edit" : "Preview"; |
render() { |
||||
return ( |
if (!this.props.user.hasSignedUp) { |
||||
<div> |
this.props.history.push('/signup'); |
||||
{this.state.previewEnabled && |
return (null); |
||||
<NewTopicPreview |
|
||||
date={this.state.previewDate} |
|
||||
subject={this.state.topicSubjectInput} |
|
||||
content={this.state.topicMessageInput} |
|
||||
/> |
|
||||
} |
|
||||
<Form> |
|
||||
{!this.state.previewEnabled && |
|
||||
[<Form.Field key={"topicSubjectInput"}> |
|
||||
<Form.Input name={"topicSubjectInput"} |
|
||||
error={this.state.topicSubjectInputEmptySubmit} |
|
||||
type="text" |
|
||||
value={this.state.topicSubjectInput} |
|
||||
placeholder="Subject" |
|
||||
id="topicSubjectInput" |
|
||||
onChange={this.handleInputChange} /> |
|
||||
</Form.Field>, |
|
||||
<TextArea key={"topicMessageInput"} |
|
||||
name={"topicMessageInput"} |
|
||||
className={this.state.topicMessageInputEmptySubmit ? "form-textarea-required" : ""} |
|
||||
value={this.state.topicMessageInput} |
|
||||
placeholder="Post" |
|
||||
id="topicMessageInput" |
|
||||
rows={5} |
|
||||
autoHeight |
|
||||
onChange={this.handleInputChange} />] |
|
||||
} |
|
||||
<br/><br/> |
|
||||
<Button.Group> |
|
||||
<Button animated key="submit" type="button" color='teal' |
|
||||
onClick={this.validateAndPost}> |
|
||||
<Button.Content visible>Post</Button.Content> |
|
||||
<Button.Content hidden> |
|
||||
<Icon name='send' /> |
|
||||
</Button.Content> |
|
||||
</Button> |
|
||||
<Button type="button" color='yellow' |
|
||||
onClick={this.handlePreviewToggle}> |
|
||||
{previewEditText} |
|
||||
</Button> |
|
||||
</Button.Group> |
|
||||
</Form> |
|
||||
</div> |
|
||||
); |
|
||||
} |
} |
||||
|
|
||||
|
const previewEditText = this.state.previewEnabled ? 'Edit' : 'Preview'; |
||||
|
return ( |
||||
|
<div> |
||||
|
{this.state.previewEnabled |
||||
|
&& ( |
||||
|
<NewTopicPreview |
||||
|
date={this.state.previewDate} |
||||
|
subject={this.state.topicSubjectInput} |
||||
|
content={this.state.topicMessageInput} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
<Form> |
||||
|
{!this.state.previewEnabled |
||||
|
&& [ |
||||
|
<Form.Field key="topicSubjectInput"> |
||||
|
<Form.Input |
||||
|
name="topicSubjectInput" |
||||
|
error={this.state.topicSubjectInputEmptySubmit} |
||||
|
type="text" |
||||
|
value={this.state.topicSubjectInput} |
||||
|
placeholder="Subject" |
||||
|
id="topicSubjectInput" |
||||
|
onChange={this.handleInputChange} |
||||
|
/> |
||||
|
</Form.Field>, |
||||
|
<TextArea |
||||
|
key="topicMessageInput" |
||||
|
name="topicMessageInput" |
||||
|
className={this.state.topicMessageInputEmptySubmit |
||||
|
? 'form-textarea-required' |
||||
|
: ''} |
||||
|
value={this.state.topicMessageInput} |
||||
|
placeholder="Post" |
||||
|
id="topicMessageInput" |
||||
|
rows={5} |
||||
|
autoHeight |
||||
|
onChange={this.handleInputChange} |
||||
|
/>] |
||||
|
} |
||||
|
<br /> |
||||
|
<br /> |
||||
|
<Button.Group> |
||||
|
<Button |
||||
|
animated |
||||
|
key="submit" |
||||
|
type="button" |
||||
|
color="teal" |
||||
|
onClick={this.validateAndPost} |
||||
|
> |
||||
|
<Button.Content visible>Post</Button.Content> |
||||
|
<Button.Content hidden> |
||||
|
<Icon name="send" /> |
||||
|
</Button.Content> |
||||
|
</Button> |
||||
|
<Button |
||||
|
type="button" |
||||
|
color="yellow" |
||||
|
onClick={this.handlePreviewToggle} |
||||
|
> |
||||
|
{previewEditText} |
||||
|
</Button> |
||||
|
</Button.Group> |
||||
|
</Form> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
orbitDB: state.orbitDB, |
||||
orbitDB: state.orbitDB, |
user: state.user |
||||
user: state.user |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(StartTopicContainer); |
export default connect(mapStateToProps)(StartTopicContainer); |
||||
|
@ -1,130 +1,139 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
import { withRouter } from 'react-router-dom' |
import { withRouter } from 'react-router-dom'; |
||||
|
|
||||
import { Message } from 'semantic-ui-react'; |
import { Message } from 'semantic-ui-react'; |
||||
|
|
||||
class RightSideBar extends Component { |
class RightSideBar extends Component { |
||||
constructor(props, context) { |
constructor(props, context) { |
||||
super(props); |
super(props); |
||||
|
|
||||
this.handleMessageClick = this.handleMessageClick.bind(this); |
this.handleMessageClick = this.handleMessageClick.bind(this); |
||||
this.handleMessageDismiss = this.handleMessageDismiss.bind(this); |
this.handleMessageDismiss = this.handleMessageDismiss.bind(this); |
||||
|
|
||||
this.state = { |
this.state = { |
||||
isTransactionMessageDismissed: [] |
isTransactionMessageDismissed: [] |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
handleMessageClick(index) { |
||||
|
const transactionHash = this.props.transactionStack[index]; |
||||
|
if (this.props.transactions[transactionHash]) { |
||||
|
if (this.props.transactions[transactionHash].status === 'error') { |
||||
|
this.handleMessageDismiss(null, index); |
||||
|
} else if (this.props.transactions[transactionHash].receipt |
||||
|
&& this.props.transactions[transactionHash].receipt.events) { |
||||
|
switch (Object.keys( |
||||
|
this.props.transactions[transactionHash].receipt.events, |
||||
|
)[0]) { |
||||
|
case 'UserSignedUp': |
||||
|
this.props.history.push('/profile'); |
||||
|
this.handleMessageDismiss(null, index); |
||||
|
break; |
||||
|
case 'UsernameUpdated': |
||||
|
this.props.history.push('/profile'); |
||||
|
this.handleMessageDismiss(null, index); |
||||
|
break; |
||||
|
case 'TopicCreated': |
||||
|
this.props.history.push(`/topic/${ |
||||
|
this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID}`);
|
||||
|
this.handleMessageDismiss(null, index); |
||||
|
break; |
||||
|
case 'PostCreated': |
||||
|
this.props.history.push(`/topic/${ |
||||
|
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID |
||||
|
}/${ |
||||
|
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID}`);
|
||||
|
this.handleMessageDismiss(null, index); |
||||
|
break; |
||||
|
default: |
||||
|
this.handleMessageDismiss(null, index); |
||||
|
break; |
||||
} |
} |
||||
|
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
handleMessageClick(index) { |
handleMessageDismiss(event, messageIndex) { |
||||
let transactionHash = this.props.transactionStack[index]; |
if (event !== null) { |
||||
if (this.props.transactions[transactionHash]) { |
event.stopPropagation(); |
||||
if (this.props.transactions[transactionHash].status === 'error') { |
|
||||
this.handleMessageDismiss(null, index); |
|
||||
} else { |
|
||||
if (this.props.transactions[transactionHash].receipt && |
|
||||
this.props.transactions[transactionHash].receipt.events) { |
|
||||
switch (Object.keys(this.props.transactions[transactionHash].receipt.events)[0]){ |
|
||||
case 'UserSignedUp': |
|
||||
this.props.history.push("/profile"); |
|
||||
this.handleMessageDismiss(null, index); |
|
||||
break; |
|
||||
case 'UsernameUpdated': |
|
||||
this.props.history.push("/profile"); |
|
||||
this.handleMessageDismiss(null, index); |
|
||||
break; |
|
||||
case 'TopicCreated': |
|
||||
this.props.history.push("/topic/" + |
|
||||
this.props.transactions[transactionHash].receipt.events.TopicCreated.returnValues.topicID |
|
||||
); |
|
||||
this.handleMessageDismiss(null, index); |
|
||||
break; |
|
||||
case 'PostCreated': |
|
||||
this.props.history.push("/topic/" + |
|
||||
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.topicID + |
|
||||
"/" + |
|
||||
this.props.transactions[transactionHash].receipt.events.PostCreated.returnValues.postID |
|
||||
); |
|
||||
this.handleMessageDismiss(null, index); |
|
||||
break; |
|
||||
default: |
|
||||
this.handleMessageDismiss(null, index); |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
handleMessageDismiss(event, messageIndex) { |
const isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice(); |
||||
if (event !== null) { |
isTransactionMessageDismissedShallowCopy[messageIndex] = true; |
||||
event.stopPropagation(); |
this.setState({ |
||||
} |
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy |
||||
|
}); |
||||
|
} |
||||
|
|
||||
let isTransactionMessageDismissedShallowCopy = this.state.isTransactionMessageDismissed.slice(); |
render() { |
||||
isTransactionMessageDismissedShallowCopy[messageIndex] = true; |
if (this.props.transactionStack.length === 0) { |
||||
this.setState({ |
return null; |
||||
isTransactionMessageDismissed: isTransactionMessageDismissedShallowCopy |
|
||||
}); |
|
||||
} |
} |
||||
|
|
||||
render() { |
const transactionMessages = this.props.transactionStack.map( |
||||
if (this.props.transactionStack.length === 0){ |
(transaction, index) => { |
||||
return null; |
if (this.state.isTransactionMessageDismissed[index]) { |
||||
|
return null; |
||||
} |
} |
||||
|
|
||||
let transactionMessages = this.props.transactionStack.map((transaction, index) => { |
let color = 'black'; |
||||
if (this.state.isTransactionMessageDismissed[index]){ |
const message = []; |
||||
return null; |
message.push( |
||||
} |
'New transaction has been queued and is waiting your confirmation.', |
||||
|
); |
||||
let color = 'black'; |
if (this.props.transactions[transaction]) { |
||||
let message = []; |
message.push(<br key="confirmed" />); |
||||
message.push("New transaction has been queued and is waiting your confirmation."); |
message.push('- transaction confirmed'); |
||||
if (this.props.transactions[transaction]) { |
} |
||||
message.push(<br key="confirmed"/>); |
if (this.props.transactions[transaction] |
||||
message.push("- transaction confirmed"); |
&& this.props.transactions[transaction].status === 'success') { |
||||
} |
/* Transaction completed successfully */ |
||||
if (this.props.transactions[transaction] && |
message.push(<br key="mined" />); |
||||
this.props.transactions[transaction].status === 'success') { |
message.push('- transaction mined'); |
||||
/* Transaction completed successfully */ |
color = 'green'; |
||||
message.push(<br key="mined"/>); |
message.push(<br key="success" />); |
||||
message.push("- transaction mined"); |
message.push('- transaction completed successfully'); |
||||
color = 'green'; |
} else if (this.props.transactions[transaction] |
||||
message.push(<br key="success"/>); |
&& this.props.transactions[transaction].status === 'error') { |
||||
message.push("- transaction completed successfully"); |
/* Transaction failed to complete */ |
||||
} else if (this.props.transactions[transaction] && |
message.push(<br key="mined" />); |
||||
this.props.transactions[transaction].status === "error"){ |
message.push('- transaction mined'); |
||||
/* Transaction failed to complete */ |
color = 'red'; |
||||
message.push(<br key="mined"/>); |
message.push(<br key="fail" />); |
||||
message.push("- transaction mined"); |
message.push('Transaction failed to complete!'); |
||||
color = 'red'; |
} |
||||
message.push(<br key="fail"/>); |
|
||||
message.push("Transaction failed to complete!"); |
|
||||
} |
|
||||
|
|
||||
return ( |
return ( |
||||
<div className="sidebar-message" key={index} |
<div |
||||
onClick={() => {this.handleMessageClick(index)}} > |
className="sidebar-message" |
||||
<Message color={color} |
key={index} |
||||
onDismiss={(e) => {this.handleMessageDismiss(e, index)}}> |
onClick={() => { this.handleMessageClick(index); }} |
||||
{message} |
> |
||||
</Message> |
<Message |
||||
</div> |
color={color} |
||||
); |
onDismiss={(e) => { |
||||
}); |
this.handleMessageDismiss(e, index); |
||||
|
}} |
||||
|
> |
||||
|
{message} |
||||
|
</Message> |
||||
|
</div> |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
return (transactionMessages); |
return (transactionMessages); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
transactions: state.transactions, |
||||
transactions: state.transactions, |
transactionStack: state.transactionStack |
||||
transactionStack: state.transactionStack |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
const RightSideBarContainer = withRouter(connect(mapStateToProps)(RightSideBar)); |
const RightSideBarContainer = withRouter( |
||||
|
connect(mapStateToProps)(RightSideBar), |
||||
|
); |
||||
|
|
||||
export default RightSideBarContainer; |
export default RightSideBarContainer; |
@ -1,175 +1,196 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from "react-redux"; |
import { connect } from 'react-redux'; |
||||
|
|
||||
import { Button, Message, Form, Dimmer, Loader, Header } from 'semantic-ui-react'; |
import { Button, Dimmer, Form, Header, Loader, Message } from 'semantic-ui-react'; |
||||
|
|
||||
import { drizzle } from '../index'; |
import { drizzle } from '../index'; |
||||
import { createDatabases } from '../utils/orbitUtils'; |
import { createDatabases } from '../utils/orbitUtils'; |
||||
import { updateUsername } from '../redux/actions/transactionsActions'; |
import { updateUsername } from '../redux/actions/transactionsActions'; |
||||
|
|
||||
const contract = "Forum"; |
const contract = 'Forum'; |
||||
const checkUsernameTakenMethod = "isUserNameTaken"; |
const checkUsernameTakenMethod = 'isUserNameTaken'; |
||||
const signUpMethod = "signUp"; |
const signUpMethod = 'signUp'; |
||||
|
|
||||
class UsernameFormContainer extends Component { |
class UsernameFormContainer extends Component { |
||||
constructor(props) { |
constructor(props) { |
||||
super(props); |
super(props); |
||||
|
|
||||
this.handleInputChange = this.handleInputChange.bind(this); |
this.handleInputChange = this.handleInputChange.bind(this); |
||||
this.handleSubmit = this.handleSubmit.bind(this); |
this.handleSubmit = this.handleSubmit.bind(this); |
||||
this.completeAction = this.completeAction.bind(this); |
this.completeAction = this.completeAction.bind(this); |
||||
this.checkedUsernames = []; |
this.checkedUsernames = []; |
||||
|
|
||||
this.state = { |
this.state = { |
||||
usernameInput: '', |
usernameInput: '', |
||||
error: false, |
error: false, |
||||
errorHeader: "", |
errorHeader: '', |
||||
errorMessage: "", |
errorMessage: '', |
||||
signingUp: false |
signingUp: false |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
|
handleInputChange(e, { name, value }) { |
||||
|
this.setState({ |
||||
|
[name]: value, |
||||
|
error: false |
||||
|
}); |
||||
|
if (value !== '') { |
||||
|
if (this.checkedUsernames.length > 0) { |
||||
|
if (this.checkedUsernames.some(e => e.usernameChecked === value)) { |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
handleInputChange(e, { name, value }) { |
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall( |
||||
|
value, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleSubmit() { |
||||
|
if (this.state.usernameInput === '') { |
||||
|
this.setState({ |
||||
|
error: true, |
||||
|
errorHeader: 'Data Incomplete', |
||||
|
errorMessage: 'You need to provide a username' |
||||
|
}); |
||||
|
} else if (!this.state.error) { |
||||
|
// Makes sure current input username has been checked for availability
|
||||
|
if (this.checkedUsernames.some( |
||||
|
e => e.usernameChecked === this.state.usernameInput, |
||||
|
)) { |
||||
|
this.completeAction(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async completeAction() { |
||||
|
if (this.props.user.hasSignedUp) { |
||||
|
this.props.dispatch(updateUsername(...[this.state.usernameInput], null)); |
||||
|
} else { |
||||
|
this.setState({ |
||||
|
signingUp: true |
||||
|
}); |
||||
|
const orbitdbInfo = await createDatabases(); |
||||
|
this.stackId = drizzle.contracts[contract].methods[signUpMethod].cacheSend( |
||||
|
...[ |
||||
|
this.state.usernameInput, |
||||
|
orbitdbInfo.identityId, |
||||
|
orbitdbInfo.identityPublicKey, |
||||
|
orbitdbInfo.identityPrivateKey, |
||||
|
orbitdbInfo.orbitId, |
||||
|
orbitdbInfo.orbitPublicKey, |
||||
|
orbitdbInfo.orbitPrivateKey, |
||||
|
orbitdbInfo.topicsDB, |
||||
|
orbitdbInfo.postsDB |
||||
|
], { |
||||
|
from: this.props.account |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
this.setState({ |
||||
|
usernameInput: '' |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate() { |
||||
|
if (this.state.signingUp) { |
||||
|
const txHash = this.props.transactionStack[this.stackId]; |
||||
|
if (txHash |
||||
|
&& this.props.transactions[txHash] |
||||
|
&& this.props.transactions[txHash].status === 'error') { |
||||
this.setState({ |
this.setState({ |
||||
[name]: value, |
signingUp: false |
||||
error: false |
|
||||
}); |
}); |
||||
if (value !== '') { |
} |
||||
if (this.checkedUsernames.length > 0) { |
} else { |
||||
if (this.checkedUsernames.some(e => e.usernameChecked === value)){ |
const temp = Object.values( |
||||
return; |
this.props.contracts[contract][checkUsernameTakenMethod], |
||||
} |
); |
||||
} |
this.checkedUsernames = temp.map(checked => ({ |
||||
|
usernameChecked: checked.args[0], |
||||
drizzle.contracts[contract].methods[checkUsernameTakenMethod].cacheCall(value); |
isTaken: checked.value |
||||
} |
})); |
||||
} |
|
||||
|
if (this.checkedUsernames.length > 0) { |
||||
handleSubmit() { |
this.checkedUsernames.forEach((checked) => { |
||||
if (this.state.usernameInput === ''){ |
if (checked.usernameChecked === this.state.usernameInput |
||||
|
&& checked.isTaken && !this.state.error) { |
||||
this.setState({ |
this.setState({ |
||||
error: true, |
error: true, |
||||
errorHeader: "Data Incomplete", |
errorHeader: 'Data disapproved', |
||||
errorMessage: "You need to provide a username" |
errorMessage: 'This username is already taken' |
||||
}); |
}); |
||||
} else if (!this.state.error) { |
} |
||||
// Makes sure current input username has been checked for availability
|
}); |
||||
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){ |
} |
||||
this.completeAction(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async completeAction() { |
|
||||
if(this.props.user.hasSignedUp){ |
|
||||
this.props.dispatch(updateUsername(...[this.state.usernameInput], null)); |
|
||||
} else { |
|
||||
this.setState({ signingUp: true }); |
|
||||
const orbitdbInfo = await createDatabases(); |
|
||||
this.stackId = drizzle.contracts[contract].methods[signUpMethod] |
|
||||
.cacheSend(...[this.state.usernameInput, |
|
||||
orbitdbInfo.identityId, |
|
||||
orbitdbInfo.identityPublicKey, |
|
||||
orbitdbInfo.identityPrivateKey, |
|
||||
orbitdbInfo.orbitId, |
|
||||
orbitdbInfo.orbitPublicKey, |
|
||||
orbitdbInfo.orbitPrivateKey, |
|
||||
orbitdbInfo.topicsDB, |
|
||||
orbitdbInfo.postsDB |
|
||||
], { from: this.props.account}); |
|
||||
} |
|
||||
this.setState({ usernameInput: '' }); |
|
||||
} |
} |
||||
|
} |
||||
componentDidUpdate() { |
|
||||
if (this.state.signingUp) { |
render() { |
||||
const txHash = this.props.transactionStack[this.stackId]; |
const { hasSignedUp } = this.props.user; |
||||
if (txHash && |
|
||||
this.props.transactions[txHash] && |
if (hasSignedUp !== null) { |
||||
this.props.transactions[txHash].status === "error") { |
const buttonText = hasSignedUp ? 'Update' : 'Sign Up'; |
||||
this.setState({signingUp: false}); |
const placeholderText = hasSignedUp |
||||
} |
? this.props.user.username |
||||
} else { |
: 'Username'; |
||||
const temp = Object.values(this.props.contracts[contract][checkUsernameTakenMethod]); |
const withError = this.state.error && { |
||||
this.checkedUsernames = temp.map(checked => {return { |
error: true |
||||
usernameChecked: checked.args[0], |
}; |
||||
isTaken: checked.value |
|
||||
}}); |
/* var disableSubmit = true; |
||||
|
if (this.checkedUsernames.length > 0) { |
||||
if (this.checkedUsernames.length > 0){ |
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){ |
||||
this.checkedUsernames.forEach( checked => { |
disableSubmit = false; |
||||
if (checked.usernameChecked === this.state.usernameInput && |
} |
||||
checked.isTaken && !this.state.error) { |
} else { |
||||
this.setState({ |
disableSubmit = false; |
||||
error: true, |
} |
||||
errorHeader: "Data disapproved", |
|
||||
errorMessage: "This username is already taken" |
disableSubmit = (disableSubmit || this.state.error) && {loading: true}; */ |
||||
}); |
|
||||
} |
return ( |
||||
}) |
<div> |
||||
} |
<Form onSubmit={this.handleSubmit} {...withError}> |
||||
} |
<Form.Field required> |
||||
|
<label>Username</label> |
||||
|
<Form.Input |
||||
|
placeholder={placeholderText} |
||||
|
name="usernameInput" |
||||
|
value={this.state.usernameInput} |
||||
|
onChange={this.handleInputChange} |
||||
|
/> |
||||
|
</Form.Field> |
||||
|
<Message |
||||
|
error |
||||
|
header={this.state.errorHeader} |
||||
|
content={this.state.errorMessage} |
||||
|
/> |
||||
|
<Button type="submit">{buttonText}</Button> |
||||
|
</Form> |
||||
|
<Dimmer active={this.state.signingUp} page> |
||||
|
<Header as="h2" inverted> |
||||
|
<Loader size="large"> |
||||
|
Magic elves are processing your noble |
||||
|
request. |
||||
|
</Loader> |
||||
|
</Header> |
||||
|
</Dimmer> |
||||
|
</div> |
||||
|
); |
||||
} |
} |
||||
|
|
||||
render() { |
return (null); |
||||
const hasSignedUp = this.props.user.hasSignedUp; |
} |
||||
|
|
||||
if(hasSignedUp !== null) { |
|
||||
const buttonText = hasSignedUp ? "Update" : "Sign Up"; |
|
||||
const placeholderText = hasSignedUp ? this.props.user.username : "Username"; |
|
||||
const withError = this.state.error && {error: true}; |
|
||||
|
|
||||
/*var disableSubmit = true; |
|
||||
if (this.checkedUsernames.length > 0) { |
|
||||
if (this.checkedUsernames.some(e => e.usernameChecked === this.state.usernameInput)){ |
|
||||
disableSubmit = false; |
|
||||
} |
|
||||
} else { |
|
||||
disableSubmit = false; |
|
||||
} |
|
||||
|
|
||||
disableSubmit = (disableSubmit || this.state.error) && {loading: true};*/ |
|
||||
|
|
||||
return( |
|
||||
<div> |
|
||||
<Form onSubmit={this.handleSubmit} {...withError}> |
|
||||
<Form.Field required> |
|
||||
<label>Username</label> |
|
||||
<Form.Input |
|
||||
placeholder={placeholderText} |
|
||||
name='usernameInput' |
|
||||
value={this.state.usernameInput} |
|
||||
onChange={this.handleInputChange} |
|
||||
/> |
|
||||
</Form.Field> |
|
||||
<Message |
|
||||
error |
|
||||
header={this.state.errorHeader} |
|
||||
content={this.state.errorMessage} |
|
||||
/> |
|
||||
<Button type='submit'>{buttonText}</Button> |
|
||||
</Form> |
|
||||
<Dimmer active={this.state.signingUp} page> |
|
||||
<Header as='h2' inverted> |
|
||||
<Loader size='large'>Magic elves are processing your noble request.</Loader> |
|
||||
</Header> |
|
||||
</Dimmer> |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
return(null); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
account: state.accounts[0], |
||||
account: state.accounts[0], |
contracts: state.contracts, |
||||
contracts: state.contracts, |
transactions: state.transactions, |
||||
transactions: state.transactions, |
transactionStack: state.transactionStack, |
||||
transactionStack: state.transactionStack, |
user: state.user |
||||
user: state.user |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(UsernameFormContainer); |
export default connect(mapStateToProps)(UsernameFormContainer); |
||||
|
@ -1,12 +1,12 @@ |
|||||
const epochTimeConverter = (timestamp) => { |
const epochTimeConverter = (timestamp) => { |
||||
var timestampDate = new Date(0); |
const timestampDate = new Date(0); |
||||
timestampDate.setUTCSeconds(timestamp); |
timestampDate.setUTCSeconds(timestamp); |
||||
return ((timestampDate.getMonth() + 1) + " " |
return (`${timestampDate.getMonth() + 1} ${ |
||||
+ timestampDate.getDate() + ", " |
timestampDate.getDate()}, ${ |
||||
+ timestampDate.getFullYear() + ", " |
timestampDate.getFullYear()}, ${ |
||||
+ timestampDate.getHours() + ":" |
timestampDate.getHours()}:${ |
||||
+ timestampDate.getMinutes() + ":" |
timestampDate.getMinutes()}:${ |
||||
+ timestampDate.getSeconds()) |
timestampDate.getSeconds()}`);
|
||||
} |
}; |
||||
|
|
||||
export default epochTimeConverter; |
export default epochTimeConverter; |
@ -1,53 +1,53 @@ |
|||||
//Action creators
|
// Action creators
|
||||
|
|
||||
export const INIT_TRANSACTION = 'INIT_TRANSACTION'; |
export const INIT_TRANSACTION = 'INIT_TRANSACTION'; |
||||
export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION'; |
export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION'; |
||||
|
|
||||
export function updateUsername(newUsername, callback){ |
export function updateUsername(newUsername, callback) { |
||||
return { |
return { |
||||
type: INIT_TRANSACTION, |
type: INIT_TRANSACTION, |
||||
transactionDescriptor: |
transactionDescriptor: |
||||
{ |
{ |
||||
contract: 'Forum', |
contract: 'Forum', |
||||
method: 'updateUsername', |
method: 'updateUsername', |
||||
params: [newUsername], |
params: [newUsername], |
||||
event: 'UsernameUpdated' |
event: 'UsernameUpdated' |
||||
}, |
}, |
||||
callback: callback |
callback |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
export function createTopic(userInputs){ |
export function createTopic(userInputs) { |
||||
return { |
return { |
||||
type: INIT_TRANSACTION, |
type: INIT_TRANSACTION, |
||||
transactionDescriptor: |
transactionDescriptor: |
||||
{ |
{ |
||||
contract: 'Forum', |
contract: 'Forum', |
||||
method: 'createTopic', |
method: 'createTopic', |
||||
params: [], |
params: [], |
||||
event: 'TopicCreated' |
event: 'TopicCreated' |
||||
}, |
}, |
||||
userInputs: userInputs |
userInputs |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
export function createPost(topicID, userInputs){ |
export function createPost(topicID, userInputs) { |
||||
return { |
return { |
||||
type: INIT_TRANSACTION, |
type: INIT_TRANSACTION, |
||||
transactionDescriptor: |
transactionDescriptor: |
||||
{ |
{ |
||||
contract: 'Forum', |
contract: 'Forum', |
||||
method: 'createPost', |
method: 'createPost', |
||||
params: [topicID], |
params: [topicID], |
||||
event: 'PostCreated' |
event: 'PostCreated' |
||||
}, |
}, |
||||
userInputs: userInputs |
userInputs |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
export function updateTransaction(transactionIndex, updateDescriptor){ |
export function updateTransaction(transactionIndex, updateDescriptor) { |
||||
return { |
return { |
||||
type: UPDATE_TRANSACTION, |
type: UPDATE_TRANSACTION, |
||||
transactionUpdates: updateDescriptor |
transactionUpdates: updateDescriptor |
||||
}; |
}; |
||||
} |
} |
@ -1,10 +1,10 @@ |
|||||
//Action creators
|
// Action creators
|
||||
|
|
||||
export const SET_NAVBAR_TITLE = 'SET_NAVBAR_TITLE'; |
export const SET_NAVBAR_TITLE = 'SET_NAVBAR_TITLE'; |
||||
|
|
||||
export function setNavBarTitle(newTitle){ |
export function setNavBarTitle(newTitle) { |
||||
return { |
return { |
||||
type: SET_NAVBAR_TITLE, |
type: SET_NAVBAR_TITLE, |
||||
title: newTitle |
title: newTitle |
||||
}; |
}; |
||||
} |
} |
||||
|
@ -1,53 +1,56 @@ |
|||||
import { IPFS_INITIALIZED, DATABASES_CREATED, DATABASES_LOADED, DATABASES_NOT_READY } from "../actions/orbitActions"; |
import { DATABASES_CREATED, |
||||
|
DATABASES_LOADED, |
||||
|
DATABASES_NOT_READY, |
||||
|
IPFS_INITIALIZED } from '../actions/orbitActions'; |
||||
|
|
||||
const initialState = { |
const initialState = { |
||||
ipfs: null, |
ipfs: null, |
||||
ipfsInitialized: false, |
ipfsInitialized: false, |
||||
ready: false, |
ready: false, |
||||
orbitdb: null, |
orbitdb: null, |
||||
topicsDB: null, |
topicsDB: null, |
||||
postsDB: null, |
postsDB: null, |
||||
id: null |
id: null |
||||
}; |
}; |
||||
|
|
||||
const orbitReducer = (state = initialState, action) => { |
const orbitReducer = (state = initialState, action) => { |
||||
switch (action.type) { |
switch (action.type) { |
||||
case IPFS_INITIALIZED: |
case IPFS_INITIALIZED: |
||||
return { |
return { |
||||
...state, |
...state, |
||||
ipfs: action.ipfs, |
ipfs: action.ipfs, |
||||
ipfsInitialized: true |
ipfsInitialized: true |
||||
}; |
}; |
||||
case DATABASES_CREATED: |
case DATABASES_CREATED: |
||||
return { |
return { |
||||
...state, |
...state, |
||||
ready: true, |
ready: true, |
||||
orbitdb: action.orbitdb, |
orbitdb: action.orbitdb, |
||||
topicsDB: action.topicsDB, |
topicsDB: action.topicsDB, |
||||
postsDB: action.postsDB, |
postsDB: action.postsDB, |
||||
id: action.id |
id: action.id |
||||
}; |
}; |
||||
case DATABASES_LOADED: |
case DATABASES_LOADED: |
||||
return { |
return { |
||||
...state, |
...state, |
||||
ready: true, |
ready: true, |
||||
orbitdb: action.orbitdb, |
orbitdb: action.orbitdb, |
||||
topicsDB: action.topicsDB, |
topicsDB: action.topicsDB, |
||||
postsDB: action.postsDB, |
postsDB: action.postsDB, |
||||
id: action.id |
id: action.id |
||||
}; |
}; |
||||
case DATABASES_NOT_READY: |
case DATABASES_NOT_READY: |
||||
return { |
return { |
||||
...state, |
...state, |
||||
ready: false, |
ready: false, |
||||
orbitdb: null, |
orbitdb: null, |
||||
topicsDB: null, |
topicsDB: null, |
||||
postsDB: null, |
postsDB: null, |
||||
id: null |
id: null |
||||
}; |
}; |
||||
default: |
default: |
||||
return state |
return state; |
||||
} |
} |
||||
}; |
}; |
||||
|
|
||||
export default orbitReducer; |
export default orbitReducer; |
||||
|
@ -1,14 +1,14 @@ |
|||||
import { combineReducers } from 'redux'; |
import { combineReducers } from 'redux'; |
||||
import { drizzleReducers } from 'drizzle'; |
import { drizzleReducers } from 'drizzle'; |
||||
import { connectRouter } from 'connected-react-router' |
import { connectRouter } from 'connected-react-router'; |
||||
import userReducer from './userReducer'; |
import userReducer from './userReducer'; |
||||
import orbitReducer from './orbitReducer'; |
import orbitReducer from './orbitReducer'; |
||||
import userInterfaceReducer from './userInterfaceReducer'; |
import userInterfaceReducer from './userInterfaceReducer'; |
||||
|
|
||||
export default (history) => combineReducers({ |
export default history => combineReducers({ |
||||
router: connectRouter(history), |
router: connectRouter(history), |
||||
user: userReducer, |
user: userReducer, |
||||
orbit: orbitReducer, |
orbit: orbitReducer, |
||||
interface: userInterfaceReducer, |
interface: userInterfaceReducer, |
||||
...drizzleReducers |
...drizzleReducers |
||||
}) |
}); |
||||
|
@ -1,18 +1,18 @@ |
|||||
import { SET_NAVBAR_TITLE } from '../actions/userInterfaceActions'; |
import { SET_NAVBAR_TITLE } from '../actions/userInterfaceActions'; |
||||
|
|
||||
const initialState = { |
const initialState = { |
||||
navBarTitle: '' |
navBarTitle: '' |
||||
}; |
}; |
||||
|
|
||||
const userInterfaceReducer = (state = initialState, action) => { |
const userInterfaceReducer = (state = initialState, action) => { |
||||
switch (action.type) { |
switch (action.type) { |
||||
case SET_NAVBAR_TITLE: |
case SET_NAVBAR_TITLE: |
||||
return { |
return { |
||||
navBarTitle: action.title |
navBarTitle: action.title |
||||
}; |
}; |
||||
default: |
default: |
||||
return state; |
return state; |
||||
} |
} |
||||
}; |
}; |
||||
|
|
||||
export default userInterfaceReducer; |
export default userInterfaceReducer; |
||||
|
@ -1,27 +1,27 @@ |
|||||
const initialState = { |
const initialState = { |
||||
username: "", |
username: '', |
||||
address: "0x0", |
address: '0x0', |
||||
avatarUrl: "", |
avatarUrl: '', |
||||
hasSignedUp: null |
hasSignedUp: null |
||||
}; |
}; |
||||
|
|
||||
const userReducer = (state = initialState, action) => { |
const userReducer = (state = initialState, action) => { |
||||
switch (action.type) { |
switch (action.type) { |
||||
case 'USER_DATA_UPDATED_(AUTHENTICATED)': |
case 'USER_DATA_UPDATED_(AUTHENTICATED)': |
||||
return { |
return { |
||||
username: action.username, |
username: action.username, |
||||
address: action.address, |
address: action.address, |
||||
hasSignedUp: true |
hasSignedUp: true |
||||
}; |
}; |
||||
case 'USER_DATA_UPDATED_(GUEST)': |
case 'USER_DATA_UPDATED_(GUEST)': |
||||
return { |
return { |
||||
username: "", |
username: '', |
||||
address: action.address, |
address: action.address, |
||||
hasSignedUp: false |
hasSignedUp: false |
||||
}; |
}; |
||||
default: |
default: |
||||
return state |
return state; |
||||
} |
} |
||||
}; |
}; |
||||
|
|
||||
export default userReducer; |
export default userReducer; |
@ -1,38 +1,37 @@ |
|||||
import { getContractInstance, getWeb3 } from "../../utils/drizzleUtils"; |
import { call, put, select, takeLatest } from 'redux-saga/effects'; |
||||
import { call, put, takeLatest, select } from 'redux-saga/effects' |
import { getContractInstance, getWeb3 } from '../../utils/drizzleUtils'; |
||||
|
|
||||
import Forum from '../../contracts/Forum'; |
import Forum from '../../contracts/Forum'; |
||||
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from "../actions/drizzleUtilsActions"; |
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; |
||||
|
|
||||
|
const accounts = state => state.accounts; |
||||
const accounts = (state) => state.accounts; |
let initFlag; let web3; let |
||||
let initFlag, web3, contract; |
contract; |
||||
|
|
||||
function* init() { |
function* init() { |
||||
if(!initFlag) { |
if (!initFlag) { |
||||
web3 = yield call(getWeb3); |
web3 = yield call(getWeb3); |
||||
contract = yield call(getContractInstance,{ |
contract = yield call(getContractInstance, { |
||||
web3: web3, |
web3, artifact: Forum |
||||
artifact: Forum |
}); |
||||
}); |
initFlag = true; |
||||
initFlag=true; |
yield put({ |
||||
yield put({type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[]}); |
type: DRIZZLE_UTILS_SAGA_INITIALIZED, ...[] |
||||
} |
}); |
||||
else |
} else console.warn('Attempted to reinitialize drizzleUtilsSaga!'); |
||||
console.warn("Attempted to reinitialize drizzleUtilsSaga!"); |
|
||||
} |
} |
||||
|
|
||||
// If the method below proves to be problematic/ineffective (i.e. getting current account
|
// If the method below proves to be problematic/ineffective (i.e. getting current account
|
||||
// from state), consider getting it from @drizzle-utils/get-accounts instead
|
// from state), consider getting it from @drizzle-utils/get-accounts instead
|
||||
// with (yield call(getAccounts, {web3}))[0];
|
// with (yield call(getAccounts, {web3}))[0];
|
||||
function* getCurrentAccount(){ |
function* getCurrentAccount() { |
||||
return (yield select(accounts))[0]; |
return (yield select(accounts))[0]; |
||||
} |
} |
||||
|
|
||||
function* drizzleUtilsSaga() { |
function* drizzleUtilsSaga() { |
||||
yield takeLatest("DRIZZLE_INITIALIZED", init); |
yield takeLatest('DRIZZLE_INITIALIZED', init); |
||||
} |
} |
||||
|
|
||||
export { web3, contract, getCurrentAccount } |
export { web3, contract, getCurrentAccount }; |
||||
|
|
||||
export default drizzleUtilsSaga; |
export default drizzleUtilsSaga; |
||||
|
@ -1,46 +1,62 @@ |
|||||
import {all, call, put, take, takeLatest} from 'redux-saga/effects' |
import { all, call, put, take, takeLatest } from 'redux-saga/effects'; |
||||
import { contract, getCurrentAccount} from './drizzleUtilsSaga'; |
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; |
||||
import { loadDatabases } from '../../utils/orbitUtils' |
import { loadDatabases } from '../../utils/orbitUtils'; |
||||
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; |
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; |
||||
import { IPFS_INITIALIZED, DATABASES_NOT_READY } from '../actions/orbitActions'; |
import { DATABASES_NOT_READY, IPFS_INITIALIZED } from '../actions/orbitActions'; |
||||
|
|
||||
let latestAccount; |
let latestAccount; |
||||
|
|
||||
function* getOrbitDBInfo() { |
function* getOrbitDBInfo() { |
||||
yield put({type: 'ORRBIT_GETTING_INFO', ...[]}); |
yield put({ |
||||
const account = yield call(getCurrentAccount); |
type: 'ORRBIT_GETTING_INFO', ...[] |
||||
if(account!==latestAccount) { |
}); |
||||
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]); |
const account = yield call(getCurrentAccount); |
||||
try { |
if (account !== latestAccount) { |
||||
const callResult = yield call(txObj1.call, {address:account}); |
const txObj1 = yield call(contract.methods.hasUserSignedUp, |
||||
if(callResult) { |
...[account]); |
||||
// console.log("Deleting local storage..");
|
try { |
||||
// localStorage.clear();
|
const callResult = yield call(txObj1.call, { |
||||
const txObj2 = yield call(contract.methods["getOrbitIdentityInfo"], ...[account]); |
address: account |
||||
const orbitIdentityInfo = yield call(txObj2.call, {address: account}); |
}); |
||||
const txObj3 = yield call(contract.methods["getOrbitDBInfo"], ...[account]); |
if (callResult) { |
||||
const orbitDBInfo = yield call(txObj3.call, {address: account}); |
// console.log("Deleting local storage..");
|
||||
yield call(loadDatabases, orbitIdentityInfo[0], orbitIdentityInfo[1], orbitIdentityInfo[2], |
// localStorage.clear();
|
||||
orbitDBInfo[0], orbitDBInfo[1], orbitDBInfo[2], orbitDBInfo[3], orbitDBInfo[4]); |
const txObj2 = yield call(contract.methods.getOrbitIdentityInfo, |
||||
} |
...[account]); |
||||
else |
const orbitIdentityInfo = yield call(txObj2.call, { |
||||
yield put({type: DATABASES_NOT_READY, ...[]}); |
address: account |
||||
|
}); |
||||
|
const txObj3 = yield call(contract.methods.getOrbitDBInfo, |
||||
|
...[account]); |
||||
|
const orbitDBInfo = yield call(txObj3.call, { |
||||
|
address: account |
||||
|
}); |
||||
|
yield call(loadDatabases, orbitIdentityInfo[0], orbitIdentityInfo[1], |
||||
|
orbitIdentityInfo[2], |
||||
|
orbitDBInfo[0], orbitDBInfo[1], orbitDBInfo[2], orbitDBInfo[3], |
||||
|
orbitDBInfo[4]); |
||||
|
} else { |
||||
|
yield put({ |
||||
|
type: DATABASES_NOT_READY, ...[] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
latestAccount=account; |
latestAccount = account; |
||||
} |
} catch (error) { |
||||
catch (error) { |
console.error(error); |
||||
console.error(error); |
yield put({ |
||||
yield put({type: 'ORBIT_SAGA_ERROR', ...[]}); |
type: 'ORBIT_SAGA_ERROR', ...[] |
||||
} |
}); |
||||
} |
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
function* orbitSaga() { |
function* orbitSaga() { |
||||
yield all([ |
yield all([ |
||||
take(DRIZZLE_UTILS_SAGA_INITIALIZED), |
take(DRIZZLE_UTILS_SAGA_INITIALIZED), |
||||
take(IPFS_INITIALIZED) |
take(IPFS_INITIALIZED) |
||||
]); |
]); |
||||
yield takeLatest("ACCOUNT_CHANGED", getOrbitDBInfo); |
yield takeLatest('ACCOUNT_CHANGED', getOrbitDBInfo); |
||||
} |
} |
||||
|
|
||||
export default orbitSaga; |
export default orbitSaga; |
||||
|
@ -1,13 +1,18 @@ |
|||||
import { all, fork } from 'redux-saga/effects' |
import { all, fork } from 'redux-saga/effects'; |
||||
import { drizzleSagas } from 'drizzle' |
import { drizzleSagas } from 'drizzle'; |
||||
import drizzleUtilsSaga from './drizzleUtilsSaga' |
import drizzleUtilsSaga from './drizzleUtilsSaga'; |
||||
import userSaga from './userSaga'; |
import userSaga from './userSaga'; |
||||
import orbitSaga from "./orbitSaga"; |
import orbitSaga from './orbitSaga'; |
||||
import transactionsSaga from "./transactionsSaga"; |
import transactionsSaga from './transactionsSaga'; |
||||
|
|
||||
export default function* root() { |
export default function* root() { |
||||
let sagas = [...drizzleSagas, drizzleUtilsSaga, orbitSaga, userSaga, transactionsSaga]; |
const sagas = [ |
||||
yield all( |
...drizzleSagas, |
||||
sagas.map(saga => fork(saga)) |
drizzleUtilsSaga, |
||||
) |
orbitSaga, |
||||
|
userSaga, |
||||
|
transactionsSaga]; |
||||
|
yield all( |
||||
|
sagas.map(saga => fork(saga)), |
||||
|
); |
||||
} |
} |
@ -1,73 +1,81 @@ |
|||||
import {call, select, take, takeEvery} from 'redux-saga/effects' |
import { call, select, take, takeEvery } from 'redux-saga/effects'; |
||||
|
|
||||
import { drizzle } from '../../index' |
import { drizzle } from '../../index'; |
||||
import { orbitSagaPut } from '../../utils/orbitUtils' |
import { orbitSagaPut } from '../../utils/orbitUtils'; |
||||
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; |
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; |
||||
|
|
||||
let transactionsHistory = Object.create(null); |
const transactionsHistory = Object.create(null); |
||||
|
|
||||
function* initTransaction(action) { |
function* initTransaction(action) { |
||||
var dataKey = drizzle.contracts[action.transactionDescriptor.contract] |
const dataKey = drizzle.contracts[action.transactionDescriptor.contract].methods[action.transactionDescriptor.method].cacheSend( |
||||
.methods[action.transactionDescriptor['method']] |
...(action.transactionDescriptor.params), |
||||
.cacheSend(...(action.transactionDescriptor.params)); |
); |
||||
|
|
||||
transactionsHistory[dataKey] = action; |
transactionsHistory[dataKey] = action; |
||||
transactionsHistory[dataKey].state = 'initialized'; |
transactionsHistory[dataKey].state = 'initialized'; |
||||
} |
} |
||||
|
|
||||
function* handleEvent(action) { |
function* handleEvent(action) { |
||||
var transactionStack = yield select((state) => state.transactionStack); |
const transactionStack = yield select(state => state.transactionStack); |
||||
var dataKey = transactionStack.indexOf(action.event.transactionHash); |
const dataKey = transactionStack.indexOf(action.event.transactionHash); |
||||
|
|
||||
switch(action.event.event) { |
switch (action.event.event) { |
||||
case 'TopicCreated': |
case 'TopicCreated': |
||||
if (dataKey !== -1 && |
if (dataKey !== -1 |
||||
transactionsHistory[dataKey] && |
&& transactionsHistory[dataKey] |
||||
transactionsHistory[dataKey].state === 'initialized') { |
&& transactionsHistory[dataKey].state === 'initialized') { |
||||
transactionsHistory[dataKey].state = 'success'; |
transactionsHistory[dataKey].state = 'success'; |
||||
//Gets orbit
|
// Gets orbit
|
||||
const orbit = yield select((state) => state.orbit); |
const orbit = yield select(state => state.orbit); |
||||
//And saves the topic
|
// And saves the topic
|
||||
yield call(orbitSagaPut, orbit.topicsDB, action.event.returnValues.topicID, |
yield call(orbitSagaPut, orbit.topicsDB, |
||||
{ subject: transactionsHistory[dataKey].userInputs.topicSubject }); |
action.event.returnValues.topicID, |
||||
yield call(orbitSagaPut, orbit.postsDB, action.event.returnValues.postID, |
{ |
||||
{ subject: transactionsHistory[dataKey].userInputs.topicSubject, |
subject: transactionsHistory[dataKey].userInputs.topicSubject |
||||
content: transactionsHistory[dataKey].userInputs.topicMessage }); |
}); |
||||
} |
yield call(orbitSagaPut, orbit.postsDB, |
||||
break; |
action.event.returnValues.postID, |
||||
case 'PostCreated': |
{ |
||||
if (dataKey !== -1 && |
subject: transactionsHistory[dataKey].userInputs.topicSubject, |
||||
transactionsHistory[dataKey] && |
content: transactionsHistory[dataKey].userInputs.topicMessage |
||||
transactionsHistory[dataKey].state === 'initialized') { |
}); |
||||
transactionsHistory[dataKey].state = 'success'; |
} |
||||
//Gets orbit
|
break; |
||||
const orbit = yield select((state) => state.orbit); |
case 'PostCreated': |
||||
//And saves the topic
|
if (dataKey !== -1 |
||||
yield call(orbitSagaPut, orbit.postsDB, action.event.returnValues.postID, |
&& transactionsHistory[dataKey] |
||||
{subject: transactionsHistory[dataKey].userInputs.postSubject, |
&& transactionsHistory[dataKey].state === 'initialized') { |
||||
content: transactionsHistory[dataKey].userInputs.postMessage }); |
transactionsHistory[dataKey].state = 'success'; |
||||
} |
// Gets orbit
|
||||
break; |
const orbit = yield select(state => state.orbit); |
||||
default: |
// And saves the topic
|
||||
//Nothing to do here
|
yield call(orbitSagaPut, orbit.postsDB, |
||||
return; |
action.event.returnValues.postID, |
||||
} |
{ |
||||
|
subject: transactionsHistory[dataKey].userInputs.postSubject, |
||||
|
content: transactionsHistory[dataKey].userInputs.postMessage |
||||
|
}); |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
// Nothing to do here
|
||||
|
} |
||||
} |
} |
||||
|
|
||||
function* handleError() { |
function* handleError() { |
||||
var transactionStack = yield select((state) => state.transactionStack); |
const transactionStack = yield select(state => state.transactionStack); |
||||
transactionStack.forEach((transaction, index) => { |
transactionStack.forEach((transaction, index) => { |
||||
if (transaction.startsWith('TEMP_')) { |
if (transaction.startsWith('TEMP_')) { |
||||
transactionsHistory[index].state = 'error'; |
transactionsHistory[index].state = 'error'; |
||||
} |
} |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
function* transactionsSaga() { |
function* transactionsSaga() { |
||||
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); |
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); |
||||
yield takeEvery("INIT_TRANSACTION", initTransaction); |
yield takeEvery('INIT_TRANSACTION', initTransaction); |
||||
yield takeEvery("EVENT_FIRED", handleEvent); |
yield takeEvery('EVENT_FIRED', handleEvent); |
||||
yield takeEvery("TX_ERROR", handleError); |
yield takeEvery('TX_ERROR', handleError); |
||||
} |
} |
||||
|
|
||||
export default transactionsSaga; |
export default transactionsSaga; |
||||
|
@ -1,53 +1,61 @@ |
|||||
import {call, put, select, take, takeEvery} from 'redux-saga/effects' |
import { call, put, select, take, takeEvery } from 'redux-saga/effects'; |
||||
|
|
||||
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; |
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; |
||||
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from "../actions/drizzleUtilsActions"; |
import { DRIZZLE_UTILS_SAGA_INITIALIZED } from '../actions/drizzleUtilsActions'; |
||||
|
|
||||
let account; |
let account; |
||||
|
|
||||
function* updateUserData() { |
function* updateUserData() { |
||||
const currentAccount = yield call(getCurrentAccount); |
const currentAccount = yield call(getCurrentAccount); |
||||
if(currentAccount!==account) { |
if (currentAccount !== account) { |
||||
account = currentAccount; |
account = currentAccount; |
||||
yield put({type: 'ACCOUNT_CHANGED', ...[]}); |
yield put({ |
||||
} |
type: 'ACCOUNT_CHANGED', ...[] |
||||
const txObj1 = yield call(contract.methods["hasUserSignedUp"], ...[account]); |
}); |
||||
try { |
} |
||||
const userState = yield call(getUserState); |
const txObj1 = yield call(contract.methods.hasUserSignedUp, ...[account]); |
||||
const callResult = yield call(txObj1.call, {address:account}); |
try { |
||||
if(callResult) { |
const userState = yield call(getUserState); |
||||
const txObj2 = yield call(contract.methods["getUsername"], ...[account]); |
const callResult = yield call(txObj1.call, { |
||||
const username = yield call(txObj2.call, {address:account}); |
address: account |
||||
if(account!==userState.address || username!==userState.username){ |
}); |
||||
const dispatchArgs = { |
if (callResult) { |
||||
address: account, |
const txObj2 = yield call(contract.methods.getUsername, ...[account]); |
||||
username: username |
const username = yield call(txObj2.call, { |
||||
}; |
address: account |
||||
yield put({type: 'USER_DATA_UPDATED_(AUTHENTICATED)', ...dispatchArgs}); |
}); |
||||
} |
if (account !== userState.address || username !== userState.username) { |
||||
} |
const dispatchArgs = { |
||||
else{ |
address: account, |
||||
if(account!==userState.address){ |
username |
||||
const dispatchArgs = { |
}; |
||||
address: account |
yield put({ |
||||
}; |
type: 'USER_DATA_UPDATED_(AUTHENTICATED)', ...dispatchArgs |
||||
yield put({type: 'USER_DATA_UPDATED_(GUEST)', ...dispatchArgs}); |
}); |
||||
} |
} |
||||
} |
} else if (account !== userState.address) { |
||||
} |
const dispatchArgs = { |
||||
catch (error) { |
address: account |
||||
console.error(error); |
}; |
||||
yield put({type: 'USER_FETCHING_ERROR', ...[]}) |
yield put({ |
||||
|
type: 'USER_DATA_UPDATED_(GUEST)', ...dispatchArgs |
||||
|
}); |
||||
} |
} |
||||
|
} catch (error) { |
||||
|
console.error(error); |
||||
|
yield put({ |
||||
|
type: 'USER_FETCHING_ERROR', ...[] |
||||
|
}); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
function* getUserState(){ |
function* getUserState() { |
||||
return yield select((state) => state.user); |
return yield select(state => state.user); |
||||
} |
} |
||||
|
|
||||
function* userSaga() { |
function* userSaga() { |
||||
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); |
yield take(DRIZZLE_UTILS_SAGA_INITIALIZED); |
||||
yield takeEvery("ACCOUNTS_FETCHED", updateUserData); |
yield takeEvery('ACCOUNTS_FETCHED', updateUserData); |
||||
} |
} |
||||
|
|
||||
export default userSaga; |
export default userSaga; |
||||
|
@ -1,28 +1,27 @@ |
|||||
import React from 'react' |
import React from 'react'; |
||||
import {connect} from 'react-redux'; |
import { connect } from 'react-redux'; |
||||
import { Route, Redirect } from 'react-router-dom' |
import { Redirect, Route } from 'react-router-dom'; |
||||
|
|
||||
const PrivateRoute = ({ component: Component, ...rest }) => ( |
const PrivateRoute = ({ component: Component, ...rest }) => ( |
||||
<Route |
<Route |
||||
{...rest} |
{...rest} |
||||
render={props => |
render={props => (props.hasSignedUp ? ( |
||||
props.hasSignedUp ? ( |
<Component {...props} /> |
||||
<Component {...props} /> |
) : ( |
||||
) : ( |
<Redirect to={{ |
||||
<Redirect to={{ |
pathname: '/signup', |
||||
pathname: "/signup", |
state: { |
||||
state: { from: props.location } |
from: props.location |
||||
}} |
} |
||||
/> |
}} |
||||
) |
/> |
||||
} |
)) |
||||
/> |
} |
||||
|
/> |
||||
); |
); |
||||
|
|
||||
const mapStateToProps = state => { |
const mapStateToProps = state => ({ |
||||
return { |
hasSignedUp: state.user.hasSignedUp |
||||
hasSignedUp: state.user.hasSignedUp, |
}); |
||||
} |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(PrivateRoute); |
export default connect(mapStateToProps)(PrivateRoute); |
||||
|
@ -1,28 +1,31 @@ |
|||||
import React from 'react' |
import React from 'react'; |
||||
import { Route, Redirect, Switch } from 'react-router-dom' |
import { Redirect, Route, Switch } from 'react-router-dom'; |
||||
import CoreLayoutContainer from '../containers/CoreLayoutContainer'; |
import CoreLayoutContainer from '../containers/CoreLayoutContainer'; |
||||
import HomeContainer from '../containers/HomeContainer' |
import HomeContainer from '../containers/HomeContainer'; |
||||
import SignUpContainer from '../containers/SignUpContainer' |
import SignUpContainer from '../containers/SignUpContainer'; |
||||
import StartTopicContainer from '../containers/StartTopicContainer' |
import StartTopicContainer from '../containers/StartTopicContainer'; |
||||
import TopicContainer from '../containers/TopicContainer' |
import TopicContainer from '../containers/TopicContainer'; |
||||
import ProfileContainer from '../containers/ProfileContainer' |
import ProfileContainer from '../containers/ProfileContainer'; |
||||
import NotFound from '../components/NotFound' |
import NotFound from '../components/NotFound'; |
||||
|
|
||||
const routes = ( |
const routes = ( |
||||
<div> |
<div> |
||||
<CoreLayoutContainer> |
<CoreLayoutContainer> |
||||
<Switch> |
<Switch> |
||||
<Route exact path="/" component={HomeContainer} /> |
<Route exact path="/" component={HomeContainer} /> |
||||
<Redirect from='/home' to="/" /> |
<Redirect from="/home" to="/" /> |
||||
<Route path="/signup" component={SignUpContainer} /> |
<Route path="/signup" component={SignUpContainer} /> |
||||
<Route path="/startTopic" component={StartTopicContainer} /> |
<Route path="/startTopic" component={StartTopicContainer} /> |
||||
<Route path="/topic/:topicId/:postId?" component={TopicContainer} /> |
<Route path="/topic/:topicId/:postId?" component={TopicContainer} /> |
||||
<Route path='/profile/:address?/:username?' component={ProfileContainer} /> |
<Route |
||||
<Route path='/404' component={NotFound} /> |
path="/profile/:address?/:username?" |
||||
<Route component={NotFound} /> |
component={ProfileContainer} |
||||
</Switch> |
/> |
||||
</CoreLayoutContainer> |
<Route path="/404" component={NotFound} /> |
||||
</div> |
<Route component={NotFound} /> |
||||
|
</Switch> |
||||
|
</CoreLayoutContainer> |
||||
|
</div> |
||||
); |
); |
||||
|
|
||||
export default routes |
export default routes; |
||||
|
@ -1,88 +1,86 @@ |
|||||
// See also: https://github.com/trufflesuite/drizzle-utils
|
// See also: https://github.com/trufflesuite/drizzle-utils
|
||||
const Web3 = require("web3"); |
const Web3 = require('web3'); |
||||
|
|
||||
const resolveWeb3 = (resolve, options, isBrowser) => { |
const resolveWeb3 = (resolve, options, isBrowser) => { |
||||
let provider; |
let provider; |
||||
|
|
||||
if (options.customProvider) { |
if (options.customProvider) { |
||||
// use custom provider from options object
|
// use custom provider from options object
|
||||
provider = options.customProvider; |
provider = options.customProvider; |
||||
} else if (isBrowser && window.ethereum) { |
} else if (isBrowser && window.ethereum) { |
||||
// use `ethereum` object injected by MetaMask
|
// use `ethereum` object injected by MetaMask
|
||||
provider = window.ethereum; |
provider = window.ethereum; |
||||
} else if (isBrowser && typeof window.web3 !== "undefined") { |
} else if (isBrowser && typeof window.web3 !== 'undefined') { |
||||
// use injected web3 object by legacy dapp browsers
|
// use injected web3 object by legacy dapp browsers
|
||||
provider = window.web3.currentProvider; |
provider = window.web3.currentProvider; |
||||
} else if (options.fallbackProvider) { |
} else if (options.fallbackProvider) { |
||||
// use fallback provider from options object
|
// use fallback provider from options object
|
||||
provider = options.fallbackProvider; |
provider = options.fallbackProvider; |
||||
} else { |
} else { |
||||
// connect to development blockchain from `truffle develop`
|
// connect to development blockchain from `truffle develop`
|
||||
provider = new Web3.providers.HttpProvider("http://127.0.0.1:9545"); |
provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545'); |
||||
} |
} |
||||
|
|
||||
const web3 = new Web3(provider); |
const web3 = new Web3(provider); |
||||
resolve(web3); |
resolve(web3); |
||||
}; |
}; |
||||
|
|
||||
const getWeb3 = (options = {}) => |
const getWeb3 = (options = { |
||||
new Promise(resolve => { |
}) => new Promise((resolve) => { |
||||
// handle server-side and React Native environments
|
// handle server-side and React Native environments
|
||||
const isReactNative = |
const isReactNative = typeof navigator !== 'undefined' && navigator.product |
||||
typeof navigator !== "undefined" && navigator.product === "ReactNative"; |
=== 'ReactNative'; |
||||
const isNode = typeof window === "undefined"; |
const isNode = typeof window === 'undefined'; |
||||
if (isNode || isReactNative) { |
if (isNode || isReactNative) { |
||||
return resolveWeb3(resolve, options, false); |
return resolveWeb3(resolve, options, false); |
||||
} |
} |
||||
|
|
||||
// if page is ready, resolve for web3 immediately
|
// if page is ready, resolve for web3 immediately
|
||||
if (document.readyState === `complete`) { |
if (document.readyState === 'complete') { |
||||
return resolveWeb3(resolve, options, true); |
return resolveWeb3(resolve, options, true); |
||||
} |
} |
||||
|
|
||||
// otherwise, resolve for web3 when page is done loading
|
// otherwise, resolve for web3 when page is done loading
|
||||
return window.addEventListener("load", () => |
return window.addEventListener('load', () => resolveWeb3(resolve, options, true)); |
||||
resolveWeb3(resolve, options, true), |
}); |
||||
); |
|
||||
}); |
|
||||
|
|
||||
const getContractInstance = (options = {}) => |
const getContractInstance = (options = { |
||||
new Promise(async (resolve, reject) => { |
}) => new Promise(async (resolve, reject) => { |
||||
if (!options.web3) { |
if (!options.web3) { |
||||
return reject(new Error("The options object with web3 is required.")); |
return reject(new Error('The options object with web3 is required.')); |
||||
} |
} |
||||
|
|
||||
const { web3 } = options; |
const { web3 } = options; |
||||
|
|
||||
let instance; |
let instance; |
||||
try { |
try { |
||||
if (options.artifact) { |
if (options.artifact) { |
||||
// if artifact exists, attempt to get network ID and the deployed address
|
// if artifact exists, attempt to get network ID and the deployed address
|
||||
const { artifact } = options; |
const { artifact } = options; |
||||
const networkId = await web3.eth.net.getId(); // web3 v1.0.0-beta.47 breaks here
|
const networkId = await web3.eth.net.getId(); // web3 v1.0.0-beta.47 breaks here
|
||||
const deployedNetwork = artifact.networks[networkId]; |
const deployedNetwork = artifact.networks[networkId]; |
||||
|
|
||||
// if no deployed address is found, instantiate without the address
|
// if no deployed address is found, instantiate without the address
|
||||
const address = deployedNetwork && deployedNetwork.address; |
const address = deployedNetwork && deployedNetwork.address; |
||||
|
|
||||
instance = new web3.eth.Contract(artifact.abi, address); |
instance = new web3.eth.Contract(artifact.abi, address); |
||||
} else if (options.abi) { |
} else if (options.abi) { |
||||
// otherwise, use passed-in ABI and deployed address (optional)
|
// otherwise, use passed-in ABI and deployed address (optional)
|
||||
const { abi, address } = options; |
const { abi, address } = options; |
||||
|
|
||||
instance = new web3.eth.Contract(abi, address); |
instance = new web3.eth.Contract(abi, address); |
||||
} else { |
} else { |
||||
return reject( |
return reject( |
||||
new Error( |
new Error( |
||||
"You must pass in a contract artifact or the ABI of a deployed contract.", |
'You must pass in a contract artifact or the ABI of a deployed contract.', |
||||
), |
), |
||||
); |
); |
||||
} |
} |
||||
|
|
||||
return resolve(instance); |
return resolve(instance); |
||||
} catch (err) { |
} catch (err) { |
||||
return reject(err); |
return reject(err); |
||||
} |
} |
||||
}); |
}); |
||||
|
|
||||
export { getWeb3, getContractInstance }; |
export { getWeb3, getContractInstance }; |
@ -1,69 +1,77 @@ |
|||||
import OrbitDB from 'orbit-db'; |
import OrbitDB from 'orbit-db'; |
||||
import Keystore from 'orbit-db-keystore'; |
import Keystore from 'orbit-db-keystore'; |
||||
import path from 'path'; |
import path from 'path'; |
||||
|
import IPFS from 'ipfs'; |
||||
import store from '../redux/store'; |
import store from '../redux/store'; |
||||
import { DATABASES_CREATED, DATABASES_LOADED, IPFS_INITIALIZED, updateDatabases } from '../redux/actions/orbitActions'; |
import { DATABASES_CREATED, DATABASES_LOADED, IPFS_INITIALIZED, updateDatabases } from '../redux/actions/orbitActions'; |
||||
import IPFS from "ipfs"; |
import ipfsOptions from '../config/ipfsOptions'; |
||||
import ipfsOptions from "../config/ipfsOptions"; |
|
||||
|
|
||||
function initIPFS(){ |
function initIPFS() { |
||||
const ipfs = new IPFS(ipfsOptions); |
const ipfs = new IPFS(ipfsOptions); |
||||
ipfs.on('ready', async () => { |
ipfs.on('ready', async () => { |
||||
store.dispatch({type: IPFS_INITIALIZED, ipfs}); |
store.dispatch({ |
||||
console.log("IPFS initialized."); |
type: IPFS_INITIALIZED, ipfs |
||||
}); |
}); |
||||
|
console.log('IPFS initialized.'); |
||||
|
}); |
||||
} |
} |
||||
|
|
||||
async function createDatabases() { |
async function createDatabases() { |
||||
console.log("Creating databases..."); |
console.log('Creating databases...'); |
||||
const ipfs = getIPFS(); |
const ipfs = getIPFS(); |
||||
const orbitdb = await new OrbitDB(ipfs); |
const orbitdb = await new OrbitDB(ipfs); |
||||
const topicsDB = await orbitdb.keyvalue('topics'); |
const topicsDB = await orbitdb.keyvalue('topics'); |
||||
const postsDB = await orbitdb.keyvalue('posts'); |
const postsDB = await orbitdb.keyvalue('posts'); |
||||
store.dispatch(updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB)); |
store.dispatch( |
||||
|
updateDatabases(DATABASES_CREATED, orbitdb, topicsDB, postsDB), |
||||
|
); |
||||
|
|
||||
const orbitKey = orbitdb.keystore.getKey(orbitdb.id); |
const orbitKey = orbitdb.keystore.getKey(orbitdb.id); |
||||
|
|
||||
return { |
return { |
||||
identityId: "Tempus", |
identityId: 'Tempus', |
||||
identityPublicKey: "edax", |
identityPublicKey: 'edax', |
||||
identityPrivateKey: "rerum", |
identityPrivateKey: 'rerum', |
||||
orbitId: orbitdb.id, |
orbitId: orbitdb.id, |
||||
orbitPublicKey: orbitKey.getPublic('hex'), |
orbitPublicKey: orbitKey.getPublic('hex'), |
||||
orbitPrivateKey: orbitKey.getPrivate('hex'), |
orbitPrivateKey: orbitKey.getPrivate('hex'), |
||||
topicsDB: topicsDB.address.root, |
topicsDB: topicsDB.address.root, |
||||
postsDB: postsDB.address.root |
postsDB: postsDB.address.root |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
async function loadDatabases(identityId, identityPublicKey, identityPrivateKey, |
async function loadDatabases(identityId, identityPublicKey, identityPrivateKey, |
||||
orbitId, orbitPublicKey, orbitPrivateKey, topicsDBId, postsDBId) { |
orbitId, orbitPublicKey, orbitPrivateKey, |
||||
console.log("Loading databases..."); |
topicsDBId, postsDBId) { |
||||
let directory = "./orbitdb"; |
console.log('Loading databases...'); |
||||
let keystore = Keystore.create(path.join(directory, orbitId, '/keystore')); |
const directory = './orbitdb'; |
||||
|
const keystore = Keystore.create(path.join(directory, orbitId, '/keystore')); |
||||
|
|
||||
keystore._storage.setItem(orbitId, JSON.stringify({ |
keystore._storage.setItem(orbitId, JSON.stringify({ |
||||
publicKey: orbitPublicKey, |
publicKey: orbitPublicKey, |
||||
privateKey: orbitPrivateKey |
privateKey: orbitPrivateKey |
||||
})); |
})); |
||||
|
|
||||
const ipfs = getIPFS(); |
const ipfs = getIPFS(); |
||||
const orbitdb = await new OrbitDB(ipfs, directory, { peerId:orbitId, keystore:keystore}); |
const orbitdb = await new OrbitDB(ipfs, directory, |
||||
const topicsDB = await orbitdb.keyvalue('/orbitdb/' + topicsDBId +'/topics'); |
{ |
||||
const postsDB = await orbitdb.keyvalue('/orbitdb/' + postsDBId +'/posts'); |
peerId: orbitId, keystore |
||||
|
}); |
||||
|
const topicsDB = await orbitdb.keyvalue(`/orbitdb/${topicsDBId}/topics`); |
||||
|
const postsDB = await orbitdb.keyvalue(`/orbitdb/${postsDBId}/posts`); |
||||
|
|
||||
await topicsDB.load(); |
await topicsDB.load(); |
||||
await postsDB.load(); |
await postsDB.load(); |
||||
|
|
||||
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB)); |
store.dispatch(updateDatabases(DATABASES_LOADED, orbitdb, topicsDB, postsDB)); |
||||
} |
} |
||||
|
|
||||
function getIPFS(){ |
function getIPFS() { |
||||
return store.getState().orbit.ipfs; |
return store.getState().orbit.ipfs; |
||||
} |
} |
||||
|
|
||||
async function orbitSagaPut(db, key, value) { |
async function orbitSagaPut(db, key, value) { |
||||
db.put(key, value); |
db.put(key, value); |
||||
} |
} |
||||
|
|
||||
export { initIPFS, createDatabases, loadDatabases, orbitSagaPut }; |
export { initIPFS, createDatabases, loadDatabases, orbitSagaPut }; |
||||
|
@ -1,25 +1,25 @@ |
|||||
const path = require("path"); |
const path = require('path'); |
||||
|
|
||||
module.exports = { |
module.exports = { |
||||
// See <http://truffleframework.com/docs/advanced/configuration>
|
// See <http://truffleframework.com/docs/advanced/configuration>
|
||||
// to customize your Truffle configuration!
|
// to customize your Truffle configuration!
|
||||
contracts_build_directory: path.join(__dirname, "app/src/contracts"), |
contracts_build_directory: path.join(__dirname, 'app/src/contracts'), |
||||
networks: { |
networks: { |
||||
development: { |
development: { |
||||
host: "localhost", |
host: 'localhost', |
||||
port: 8545, |
port: 8545, |
||||
network_id: "*" // Match any network id
|
network_id: '*' // Match any network id
|
||||
} |
} |
||||
}, |
}, |
||||
compilers:{ |
compilers: { |
||||
solc: { |
solc: { |
||||
version: "0.5.5", |
version: '0.5.5', |
||||
settings:{ |
settings: { |
||||
optimizer: { |
optimizer: { |
||||
enabled: true, |
enabled: true, |
||||
runs: 500 |
runs: 500 |
||||
} |
|
||||
} |
|
||||
} |
} |
||||
|
} |
||||
} |
} |
||||
|
} |
||||
}; |
}; |
||||
|
Loading…
Reference in new issue