Browse Source

Extract contract interactions to separate component, Minor improvements

develop
Apostolos Fanakis 7 years ago
parent
commit
3f87cb5fe1
  1. 1
      contracts/Forum.sol
  2. 122
      src/components/Post.js
  3. 119
      src/components/PostList.js
  4. 25
      src/components/ProfileInformation.js
  5. 99
      src/components/Topic.js
  6. 120
      src/components/TopicList.js
  7. 81
      src/components/WithBlockchainData.js
  8. 59
      src/containers/BoardContainer.js
  9. 242
      src/containers/ProfileContainer.js
  10. 3
      src/containers/StartTopicContainer.js
  11. 69
      src/containers/TopicContainer.js
  12. 5
      src/index.js

1
contracts/Forum.sol

@ -41,6 +41,7 @@ contract Forum {
} }
function getUsername(address userAddress) public view returns (string) { function getUsername(address userAddress) public view returns (string) {
require (hasUserSignedUp(userAddress), "User hasn't signed up yet.");
return users[userAddress].username; return users[userAddress].username;
} }

122
src/components/Post.js

@ -1,40 +1,89 @@
import React from 'react'; import React, { Component } from 'react';
import { Link, withRouter } from 'react-router'; import { Link, withRouter } from 'react-router';
import UserAvatar from 'react-user-avatar'; import { drizzleConnect } from 'drizzle-react';
import PropTypes from 'prop-types';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter'
import UserAvatar from 'react-user-avatar';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
const Post = (props) => { class Post extends Component {
constructor(props, context) {
super(props);
this.fetchPost = this.fetchPost.bind(this);
this.orbitPostData = {
content: "",
subject: ""
};
this.orbitPostDataFetchStatus = "pending";
}
async fetchPost(postID) {
this.orbitPostDataFetchStatus = "fetching";
var som = this.props.orbitDB.postsDB.get(postID);
if (som){
this.orbitPostData = som;
}
this.orbitPostDataFetchStatus = "fetched";
}
render(){
let avatarView = (this.props.blockchainData[0].returnData
? <UserAvatar
size="40"
className="inline user-avatar"
src={this.props.avatarUrl}
name={this.props.blockchainData[0].returnData[2]}/>
: <div className="user-avatar" style={{width: "40px"}}></div>
);
return ( return (
props.post !== null <div className="pure-u-1-1 post card"
? <div className="pure-u-1-1 post card"> onClick={() => { this.context.router.push("/topic/"
+ this.props.blockchainData[0].returnData[4] + "/"
+ this.props.postID)}}>
<div className="post-header"> <div className="post-header">
<div className="vertical-center-children"> <div className="vertical-center-children">
<Link to={"/profile/" + props.post.userAddress + "/" + props.post.username} {this.props.blockchainData[0].returnData !== null
?<Link to={"/profile/" + this.props.blockchainData[0].returnData[1]
+ "/" + this.props.blockchainData[0].returnData[2]}
onClick={(event) => {event.stopPropagation()}}> onClick={(event) => {event.stopPropagation()}}>
<UserAvatar {avatarView}
size="40"
className="inline user-avatar"
src={props.post.avatarUrl}
name={props.post.username}/>
</Link> </Link>
:avatarView
}
<p className="inline no-margin"> <p className="inline no-margin">
<strong> <strong>
{props.post.username} <span style={{color: this.props.blockchainData[0].returnData !== null ? "" : "grey"}}>
{this.props.blockchainData[0].returnData !== null
?this.props.blockchainData[0].returnData[2]
:"Username"
}
</span>
<br/> <br/>
Subject: {props.post.subject} <span style={{color: this.orbitPostData.subject ? "" : "grey"}}>
Subject: {this.orbitPostData.subject}
</span>
</strong> </strong>
</p> </p>
</div> </div>
<div className="post-info"> <div className="post-info">
<span>Posted <TimeAgo date={props.post.date}/></span> <span>
<span>#{props.post.postIndex}</span> Posted {this.props.blockchainData[0].returnData !== null &&
<TimeAgo date={epochTimeConverter(this.props.blockchainData[0].returnData[3])}/>
}
</span>
<span>#{this.props.postIndex}</span>
</div> </div>
</div> </div>
<hr/> <hr/>
<div className="post-content"> <div className="post-content">
{props.post.postContent {this.orbitPostData.content
? <ReactMarkdown source={props.post.postContent} /> ? <ReactMarkdown source={this.orbitPostData.content} />
: <p style={{color: 'grey'}}>Post content...</p> : <p style={{color: 'grey'}}>Post content...</p>
} }
</div> </div>
@ -43,25 +92,26 @@ const Post = (props) => {
Maybe add buttons for upvote etc here... Maybe add buttons for upvote etc here...
</div> </div>
</div> </div>
: <div className="pure-u-1-1 post card" style={{color: 'grey'}}>
<div className="post-header">
<p className="inline no-margin">
<strong>Subject</strong>
</p>
<div className="post-info">
<span>Posted </span>
</div>
</div>
<hr/>
<div className="post-content">
<p>Post content...</p>
</div>
<hr/>
<div className="post-meta">
Maybe add buttons for upvote etc here...
</div>
</div>
); );
}
componentDidUpdate() {
if (this.props.blockchainData[0].status === "success"
&& this.orbitPostDataFetchStatus === "pending") {
this.fetchPost(this.props.postID);
}
}
};
Post.contextTypes = {
router: PropTypes.object
};
const mapStateToProps = state => {
return {
user: state.user,
orbitDB: state.orbitDB
}
}; };
export default withRouter(Post); export default drizzleConnect(withRouter(Post), mapStateToProps);

119
src/components/PostList.js

@ -1,110 +1,35 @@
import { drizzleConnect } from 'drizzle-react'; import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Post from './Post'; import WithBlockchainData from './WithBlockchainData';
import epochTimeConverter from '../helpers/EpochTimeConverter'
const contract = "Forum";
const contractMethod = "getPost";
class PostList extends Component {
constructor(props, context) {
super(props);
this.fetchPost = this.fetchPost.bind(this);
this.drizzle = context.drizzle;
this.dataKeys = [];
this.postsData = new Array(parseInt(this.props.postIDs.length, 10)).fill(undefined);
this.orbitPostsData = new Array(parseInt(this.props.postIDs.length, 10)).fill(undefined);
this.orbitPostsDataFetchStatus = new Array(parseInt(this.props.postIDs.length, 10)).fill("pending");
for (var i = 0; i < this.props.postIDs.length; ++i){
this.dataKeys[i] = this.drizzle.contracts[contract].methods[contractMethod]
.cacheCall(this.props.postIDs[i]);
}
}
async fetchPost(index) { import Post from './Post';
/*const fullAddress = this.postsData[postID][1];
const store = await this.props.orbitDB.orbitdb.keyvalue(JSON.stringify(fullAddress));
await store.load();
var som = store.get(JSON.stringify(postID));
this.orbitPostsData[postID] = som['subject'];
this.orbitPostsDataFetchStatus[postID] = "fetched";*/
var som = this.props.orbitDB.postsDB.get(this.props.postIDs[index]);
this.orbitPostsData[index] = som;
this.orbitPostsDataFetchStatus[index] = "fetched";
}
render (){ const PostList = (props) => {
const posts = this.postsData.map((post, index) => { const posts = props.postIDs.map((postID, index) => {
if (post) {
return (
<div onClick={() => {
this.context.router.push("/topic/" + post[4] + "/" +
((this.orbitPostsData[index] !== undefined)
? this.orbitPostsData[index].subject + "/" + this.props.postIDs[index]
: ""))}}
key={index}>
<Post post={{
avatarUrl: post.avatarUrl,
userAddress: post[1],
username: post[2],
subject: (this.orbitPostsData[index] !== undefined) && this.orbitPostsData[index].subject,
date: epochTimeConverter(post[3]),
postIndex: index,
postContent: (this.orbitPostsData[index] !== undefined) && this.orbitPostsData[index].content
}}
id={index}
key={index}/>
</div>
);
} else {
return ( return (
<Post post={null} <WithBlockchainData
id={index} component={Post}
key={index}/> callsInfo={[{
contract: 'Forum',
method: 'getPost',
params: [postID]
}]}
avatarUrl={""}
postIndex={index}
postID={postID}
key={postID}
/>
); );
}
}); });
return ( return (
<div className="posts-list"> <div className="posts-list">
{posts} {props.recentToTheTop
?posts.slice(0).reverse()
:posts
}
</div> </div>
); );
}
componentWillReceiveProps() {
for (var i = 0; i < this.props.postIDs.length; ++i){
if (this.postsData[i] === undefined) {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKeys[i]];
if (dataFetched){
this.postsData[i] = dataFetched.value;
}
} else if (!this.orbitPostsData[i] && this.orbitPostsDataFetchStatus[i] === "pending") {
this.orbitPostsDataFetchStatus[i] = "fetching";
this.fetchPost(i);
}
}
}
};
PostList.contextTypes = {
drizzle: PropTypes.object,
router: PropTypes.object
};
const mapStateToProps = state => {
return {
user: state.user, //Needed!!
orbitDB: state.orbitDB
}
}; };
export default drizzleConnect(PostList, mapStateToProps); export default PostList;

25
src/components/ProfileInformation.js

@ -1,23 +1,38 @@
import React from 'react'; import React from 'react';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
import epochTimeConverter from '../helpers/EpochTimeConverter';
import UsernameFormContainer from '../containers/UsernameFormContainer'; import UsernameFormContainer from '../containers/UsernameFormContainer';
const ProfileInformation = (props) => { const ProfileInformation = (props) => {
let transaction = props.blockchainData
.find(transaction => transaction.callInfo.method === "getUsername");
let username = transaction ? transaction.returnData : "";
transaction = props.blockchainData
.find(transaction => transaction.callInfo.method === "getUserDateOfRegister");
let dateOfRegister = transaction ? transaction.returnData : "";
transaction = props.blockchainData
.find(transaction => transaction.callInfo.method === "getOrbitDBId")
let orbitDBId = transaction ? transaction.returnData : "";
return ( return (
<div className="pure-u-1-1 user-info card"> <div className="pure-u-1-1 user-info card">
{props.avatarUrl && <UserAvatar {props.avatarUrl && <UserAvatar
size="40" size="40"
className="inline user-avatar" className="inline user-avatar"
src={props.avatarUrl} src={props.avatarUrl}
name={props.username}/>} name={username}/>}
<p className="no-margin inline"> <p className="no-margin inline">
<strong>Username:</strong> {props.username} <strong>Username:</strong> {username}
</p> </p>
<p className="no-margin"> <p className="no-margin">
<strong>Account address:</strong> {props.address} <strong>Account address:</strong> {props.address}
</p> </p>
<p className="no-margin"> <p className="no-margin">
<strong>OrbitDB:</strong> {props.orbitAddress} <strong>OrbitDB:</strong> {orbitDBId}
</p> </p>
<p className="no-margin"> <p className="no-margin">
<strong>Number of topics created:</strong> {props.numberOfTopics} <strong>Number of topics created:</strong> {props.numberOfTopics}
@ -25,11 +40,9 @@ const ProfileInformation = (props) => {
<p className="no-margin"> <p className="no-margin">
<strong>Number of posts:</strong> {props.numberOfPosts} <strong>Number of posts:</strong> {props.numberOfPosts}
</p> </p>
{props.dateOfRegister &&
<p className="no-margin"> <p className="no-margin">
<strong>Member since:</strong> {props.dateOfRegister} <strong>Member since:</strong> {epochTimeConverter(dateOfRegister)}
</p> </p>
}
{props.self && <UsernameFormContainer/>} {props.self && <UsernameFormContainer/>}
</div> </div>
); );

99
src/components/Topic.js

@ -1,30 +1,91 @@
import React from 'react'; import React, { Component } from 'react';
import { Link } from 'react-router';
import { drizzleConnect } from 'drizzle-react';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import epochTimeConverter from '../helpers/EpochTimeConverter'
class Topic extends Component {
constructor(props){
super(props);
this.fetchSubject = this.fetchSubject.bind(this);
this.topicSubject = null;
this.topicSubjectFetchStatus = "pending";
}
async fetchSubject(topicID) {
this.topicSubjectFetchStatus = "fetching";
const Topic = (props) => { if (this.props.blockchainData[0].returnData[1] === this.props.user.address) {
let som = this.props.orbitDB.topicsDB.get(topicID);
this.topicSubject = som['subject'];
this.topicSubjectFetchStatus = "fetched";
} else {
const fullAddress = "/orbitdb" + this.props.blockchainData[0].returnData[0] + "/topics";
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
/*store.events.on('replicated', () => {
const result = store.iterator({ limit: -1 }).collect().map(e => e.payload.value)
console.log(result.join('\n'))
})*/
await store.load();
let som = store.get(topicID);
this.topicSubject = som['subject'];
this.topicSubjectFetchStatus = "fetched";
}
}
render(){
return ( return (
props.topic !== null <Link to={"/topic/" + this.props.topicID}>
? <div className={"pure-u-1-1 topic card"}> <div className={"pure-u-1-1 topic card"}>
<p className="topic-subject" style={{color: props.topic.topicSubject ? "" : "grey"}}> <p className="topic-subject" style={{color: this.topicSubject ? "" : "grey"}}>
<strong>{props.topic.topicSubject ? props.topic.topicSubject : "Subject"}</strong> <strong>{this.topicSubject !== null ? this.topicSubject : "Subject"}</strong>
</p> </p>
<hr/> <hr/>
<div className="topic-meta"> <div className="topic-meta">
<p className="no-margin">{props.topic.topicStarter}</p> <p className="no-margin" style={{
<p className="no-margin">Number of replies: {props.topic.numberOfReplies}</p> color: this.props.blockchainData[0].returnData !== null ? "" : "grey"
<p className="topic-date">Started <TimeAgo date={props.topic.date}/></p> }}>
</div> {this.props.blockchainData[0].returnData !== null
</div> ?this.props.blockchainData[0].returnData[2]
: <div className={"pure-u-1-1 topic card"} style={{color: 'grey'}}> :"Username"
<p className="topic-subject"><strong>Subject</strong></p> }
<hr/> </p>
<div className="topic-meta"> <p className="no-margin" style={{
<p className="no-margin">Username</p> color: this.props.blockchainData[0].returnData !== null ? "" : "grey"
<p className="no-margin">Number of replies: </p> }}>
<p className="topic-date">Started </p> {"Number of replies: " + (this.props.blockchainData[0].returnData !== null
?this.props.blockchainData[0].returnData[4].length
:"")
}
</p>
<p className="topic-date">
Started {this.props.blockchainData[0].returnData !== null &&
<TimeAgo date={epochTimeConverter(this.props.blockchainData[0].returnData[3])}/>
}
</p>
</div> </div>
</div> </div>
</Link>
); );
}
componentDidUpdate(){
if (this.props.blockchainData[0].returnData !== null && this.topicSubjectFetchStatus === "pending") {
this.fetchSubject(this.props.topicID);
}
}
}; };
export default Topic; const mapStateToProps = state => {
return {
user: state.user,
orbitDB: state.orbitDB
}
}
export default drizzleConnect(Topic, mapStateToProps);

120
src/components/TopicList.js

@ -1,84 +1,23 @@
import { drizzleConnect } from 'drizzle-react'; import React from 'react';
import React, { Component } from 'react';
import { Link } from 'react-router';
import PropTypes from 'prop-types';
import Topic from './Topic'; import WithBlockchainData from './WithBlockchainData';
import epochTimeConverter from '../helpers/EpochTimeConverter'
const contract = "Forum";
const contractMethod = "getTopic";
class TopicList extends Component {
constructor(props, context) {
super(props);
this.fetchSubject = this.fetchSubject.bind(this);
this.drizzle = context.drizzle;
this.dataKeys = [];
this.topicsData = new Array(parseInt(this.props.topicIDs.length, 10)).fill(undefined);
this.topicsSubjects = [];
this.topicsSubjectsFetchStatus = new Array(parseInt(this.props.topicIDs.length, 10)).fill("pending");
for (var i = 0; i < this.props.topicIDs.length; ++i){
this.dataKeys[i] = this.drizzle.contracts[contract].methods[contractMethod]
.cacheCall(this.props.topicIDs[i]);
}
this.state = {
};
}
async fetchSubject(topicIndex) {
if (this.topicsData[topicIndex][1] === this.props.user.address){
let som =this.props.orbitDB.topicsDB.get(this.props.topicIDs[topicIndex]);
this.topicsSubjects[topicIndex] = som['subject'];
this.topicsSubjectsFetchStatus[topicIndex] = "fetched";
} else {
const fullAddress = "/orbitdb" + this.topicsData[topicIndex][0] + "/topics";
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress);
/*store.events.on('replicated', () => { import Topic from './Topic';
const result = store.iterator({ limit: -1 }).collect().map(e => e.payload.value)
console.log(result.join('\n'))
})*/
await store.load();
let som = store.get(this.props.topicIDs[topicIndex]);
this.topicsSubjects[topicIndex] = som['subject'];
this.topicsSubjectsFetchStatus[topicIndex] = "fetched";
}
}
render (){ const TopicList = (props) => {
const topics = this.topicsData.map((topic, index) => { const topics = props.topicIDs.map((topicID) => {
if (topic){
return ( return (
<Link to={"/topic/" + this.props.topicIDs[index] + "/" + <WithBlockchainData
((this.topicsSubjects[index] !== undefined) ? this.topicsSubjects[index] + "/" + 0 : "")} component={Topic}
key={index}> callsInfo={[{
<Topic topic={{ contract: 'Forum',
topicSubject: ((this.topicsSubjects[index] !== undefined) && this.topicsSubjects[index]), method: 'getTopic',
topicStarter: topic[2], params: [topicID]
numberOfReplies: topic[4].length, }]}
date: epochTimeConverter(topic[3]) topicID={topicID}
}} key={topicID}
id={index} />
key={index}/>
</Link>
); );
} else {
return (
<Link to={"/topic/" + this.props.topicIDs[index] + "/"}
key={index}>
<Topic topic={null}
id={index}
key={index}/>
</Link>
);
}
}); });
return ( return (
@ -86,33 +25,6 @@ class TopicList extends Component {
{topics.slice(0).reverse()} {topics.slice(0).reverse()}
</div> </div>
); );
}
componentWillReceiveProps() {
for (var i = 0; i < this.props.topicIDs.length; ++i){
if (this.topicsData[i] === undefined) {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKeys[i]];
if (dataFetched){
this.topicsData[i] = dataFetched.value;
}
} else if (!this.topicsSubjects[i] && this.topicsSubjectsFetchStatus[i] === "pending") {
this.topicsSubjectsFetchStatus[i] = "fetching";
this.fetchSubject(i);
}
}
}
};
TopicList.contextTypes = {
drizzle: PropTypes.object
};
const mapStateToProps = state => {
return {
user: state.user, //Needed!!
orbitDB: state.orbitDB,
}
}; };
export default drizzleConnect(TopicList, mapStateToProps); export default TopicList;

81
src/components/WithBlockchainData.js

@ -0,0 +1,81 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class WithBlockchainData extends Component {
constructor(props, context) {
super(props);
{
let {component, callsInfo, ...rest } = this.props;
this.component = component;
this.callsInfo = callsInfo;
this.forwardedProps = rest;
}
this.checkContractUpdates = this.checkContractUpdates.bind(this);
this.drizzle = context.drizzle;
this.dataKeys = [];
this.blockchainData = this.callsInfo.map((call) => {
return ({
callInfo: call,
status: "initialized",
returnData: null
});
});
for (var i = 0; i < this.callsInfo.length; ++i){
this.dataKeys[i] = this.drizzle
.contracts[this.callsInfo[i].contract]
.methods[this.callsInfo[i].method]
.cacheCall(...(this.callsInfo[i].params));
this.blockchainData[i].status = "pending";
}
this.state = {
transactionsState: new Array(this.callsInfo.length).fill("pending")
}
}
render() {
let {component, callsInfo, ...rest } = this.props;
return (
<this.component blockchainData={this.blockchainData} {...rest}/>
);
}
componentDidMount() {
this.intervalChecker = setInterval(this.checkContractUpdates, 10); //HOWMUCHMUCHACHO???
}
componentWillUnmount() {
clearInterval(this.intervalChecker);
}
checkContractUpdates() {
for (var i = 0; i < this.callsInfo.length; ++i){
let currentDrizzleState = this.drizzle.store.getState();
if (this.state.transactionsState[i] === "pending") {
let dataFetched = (currentDrizzleState
.contracts[this.callsInfo[i].contract][this.callsInfo[i].method][this.dataKeys[i]]);
if (dataFetched){
this.blockchainData[i].returnData = dataFetched.value;
this.blockchainData[i].status = "success";
this.setState((prevState) => ({
transactionsState: [
...prevState.transactionsState.slice(0, i),
"success",
...prevState.transactionsState.slice(i)
]
}));
}
} //TODO cover errors!!
}
}
}
WithBlockchainData.contextTypes = {
drizzle: PropTypes.object
};
export default WithBlockchainData;

59
src/containers/BoardContainer.js

@ -2,32 +2,28 @@ import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import WithBlockchainData from '../components/WithBlockchainData';
import TopicList from '../components/TopicList'; import TopicList from '../components/TopicList';
import FloatingButton from '../components/FloatingButton'; import FloatingButton from '../components/FloatingButton';
import LoadingSpinner from '../components/LoadingSpinner'; import LoadingSpinner from '../components/LoadingSpinner';
const contract = "Forum";
const contractMethod = "getNumberOfTopics";
class Board extends Component { class Board extends Component {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this.drizzle = context.drizzle;
this.state = {
transactionState: null
};
} }
render() { render() {
var boardContents; var boardContents;
if (this.state.transactionState !== "SUCCESS") { if (!this.props.blockchainData[0].returnData) {
boardContents = ( boardContents = (
<LoadingSpinner/> <LoadingSpinner/>
); );
} else { } else {
boardContents = <TopicList topicIDs={this.topicIDs}/>; this.topicIDs = [];
for (var i = 0; i < this.props.blockchainData[0].returnData; i++) {
this.topicIDs.push(i);
}
boardContents = <TopicList topicIDs={this.topicIDs}/>
} }
return ( return (
@ -37,28 +33,6 @@ class Board extends Component {
</div> </div>
); );
} }
componentWillReceiveProps() {
if (this.state.transactionState === null){
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized
//This gets called only once but should be called every time someone posts
this.dataKey = this.drizzle.contracts[contract].methods[contractMethod].cacheCall();
this.setState({'transactionState': "IN_PROGRESS"});
}
}
if (!this.numberOfTopics) {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKey];
if (dataFetched){
this.numberOfTopics = dataFetched.value;
this.topicIDs = [];
for (var i = 0; i < this.numberOfTopics; i++) {
this.topicIDs.push(i);
}
this.setState({'transactionState': "SUCCESS"});
}
}
}
} }
Board.contextTypes = { Board.contextTypes = {
@ -71,6 +45,23 @@ const mapStateToProps = state => {
} }
}; };
const BoardContainer = drizzleConnect(Board, mapStateToProps); class BoardContainer extends Component {
constructor(props){
super(props);
this.board = <WithBlockchainData
component={drizzleConnect(Board, mapStateToProps)}
callsInfo={[{
contract: 'Forum',
method: 'getNumberOfTopics',
params: []
}]}
/>;
}
render() {
return(this.board);
}
}
export default BoardContainer; export default BoardContainer;

242
src/containers/ProfileContainer.js

@ -2,58 +2,24 @@ import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import WithBlockchainData from '../components/WithBlockchainData';
import ProfileInformation from '../components/ProfileInformation'; import ProfileInformation from '../components/ProfileInformation';
import TopicList from '../components/TopicList'; import TopicList from '../components/TopicList';
import PostList from '../components/PostList'; import PostList from '../components/PostList';
import LoadingSpinner from '../components/LoadingSpinner'; import LoadingSpinner from '../components/LoadingSpinner';
import epochTimeConverter from '../helpers/EpochTimeConverter';
const contract = "Forum";
const contractMethods = {
getUsername: "getUsername",
getDateOfRegister: "getUserDateOfRegister",
getOrbitDB: "getOrbitDBId",
getUserTopics: "getUserTopics",
getUserPosts: "getUserPosts"
};
class Profile extends Component { class Profile extends Component {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
if (this.props.params.address){
this.profile = {
userAddress: this.props.params.address,
username: this.props.params.username ? this.props.params.username : "",
orbitId: "",
self: false
}
} else {
this.profile = {
userAddress: this.props.user.address,
username: this.props.user.username,
orbitId: this.props.orbitDB.id,
self: true
}
}
this.handleTabClick = this.handleTabClick.bind(this); this.handleTabClick = this.handleTabClick.bind(this);
this.propsToView = this.propsToView.bind(this);
this.drizzle = context.drizzle; this.drizzle = context.drizzle;
this.state = { this.state = {
viewSelected: "topics", viewSelected: "topics",
username: this.profile.username, userAddress: this.props.params.address ? this.props.params.address : this.props.user.address
userAddress: this.profile.userAddress,
dateOfRegister: null,
orbitDBId: this.profile.orbitId,
getUsernameTransactionState: null,
getDateOfRegisterTransactionState: null,
getOrbitDBTransactionState: this.profile.orbitId ? "SUCCESS" : null,
getTopicsTransactionState: null,
getPostsTransactionState: null,
topicIDs: [],
postIDs: []
}; };
} }
@ -62,15 +28,44 @@ class Profile extends Component {
} }
render() { render() {
this.propsToView();
var selectedTab = this.state.viewSelected === "topics"
?(<div className="profile-tab">
{this.topicIDs
? <TopicList topicIDs={this.topicIDs} />
: <LoadingSpinner />
}
</div>)
:(<div className="profile-tab">
{this.postIDs
? <PostList postIDs={this.postIDs} recentToTheTop />
: <LoadingSpinner />
}
</div>);
return ( return (
<div className="pure-g"> <div className="pure-g">
<ProfileInformation username={this.state.username} <WithBlockchainData
component={ProfileInformation}
callsInfo={[{
contract: 'Forum',
method: 'getUsername',
params: [this.state.userAddress]
},{
contract: 'Forum',
method: 'getUserDateOfRegister',
params: [this.state.userAddress]
},{
contract: 'Forum',
method: 'getOrbitDBId',
params: [this.state.userAddress]
}]}
address={this.state.userAddress} address={this.state.userAddress}
orbitAddress={this.state.orbitDBId} numberOfTopics={this.topicIDs && this.topicIDs.length}
numberOfTopics={this.state.topicIDs.length} numberOfPosts={this.postIDs && this.postIDs.length}
numberOfPosts={this.state.postIDs.length} self={this.state.userAddress === this.props.user.address}
dateOfRegister={this.state.dateOfRegister} key="profileInfo"
self={this.profile.self}/> />
<div className="pure-u-1-1 profile-tabs-header"> <div className="pure-u-1-1 profile-tabs-header">
<p onClick={() => (this.handleTabClick("topics"))} <p onClick={() => (this.handleTabClick("topics"))}
className={this.state.viewSelected === "topics" ? "profile-tab-selected" : ""}> className={this.state.viewSelected === "topics" ? "profile-tab-selected" : ""}>
@ -81,117 +76,24 @@ class Profile extends Component {
Posts Posts
</p> </p>
</div> </div>
{this.state.viewSelected === "topics" {selectedTab}
?<div className="profile-tab">
{this.state.getTopicsTransactionState === "SUCCESS"
? <TopicList topicIDs={this.state.topicIDs}/>
: <LoadingSpinner />
}
</div>
:<div className="profile-tab">
{this.state.getPostsTransactionState === "SUCCESS"
? <PostList postIDs={this.state.postIDs}/>
: <LoadingSpinner />
}
</div>
}
</div> </div>
); );
} }
componentWillReceiveProps() { propsToView(){
if (this.state.getUsernameTransactionState === null){ if (!this.topicIDs){
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized let transaction = this.props.blockchainData
this.usernameKey = this.drizzle.contracts[contract] .find(transaction => transaction.callInfo.method === "getUserTopics");
.methods[contractMethods.getUsername].cacheCall(this.state.userAddress); if (transaction.returnData){
this.setState({'getUsernameTransactionState': "IN_PROGRESS"}); this.topicIDs = transaction.returnData;
}
}
if (this.state.getUsernameTransactionState === "IN_PROGRESS") {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState
.contracts[contract][contractMethods.getUsername])[this.usernameKey];
if (dataFetched){
this.setState({
'username': dataFetched.value,
'getUsernameTransactionState': "SUCCESS"
});
}
}
if (this.state.getDateOfRegisterTransactionState === null){
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized
this.dateOfRegisterKey = this.drizzle.contracts[contract]
.methods[contractMethods.getDateOfRegister].cacheCall(this.state.userAddress);
this.setState({'getDateOfRegisterTransactionState': "IN_PROGRESS"});
}
}
if (this.state.getDateOfRegisterTransactionState === "IN_PROGRESS") {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState
.contracts[contract][contractMethods.getDateOfRegister])[this.dateOfRegisterKey];
if (dataFetched){
this.setState({
'dateOfRegister': epochTimeConverter(dataFetched.value),
'getDateOfRegisterTransactionState': "SUCCESS"
});
}
}
if (this.state.getOrbitDBTransactionState === null){
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized
this.orbitDBIdKey = this.drizzle.contracts[contract]
.methods[contractMethods.getOrbitDB].cacheCall(this.state.userAddress);
this.setState({'getOrbitDBTransactionState': "IN_PROGRESS"});
}
}
if (this.state.getOrbitDBTransactionState === "IN_PROGRESS") {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState
.contracts[contract][contractMethods.getOrbitDB])[this.orbitDBIdKey];
if (dataFetched){
this.setState({
'orbitDBId': dataFetched.value,
'getOrbitDBTransactionState': "SUCCESS"
});
}
}
if (this.state.getTopicsTransactionState === null){
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized
this.getTopicsKey = this.drizzle.contracts[contract]
.methods[contractMethods.getUserTopics].cacheCall(this.state.userAddress);
this.setState({'getTopicsTransactionState': "IN_PROGRESS"});
} }
} }
if (this.state.getTopicsTransactionState === "IN_PROGRESS") { if (!this.postIDs){
let currentDrizzleState = this.drizzle.store.getState(); let transaction = this.props.blockchainData
let dataFetched = (currentDrizzleState .find(transaction => transaction.callInfo.method === "getUserPosts");
.contracts[contract][contractMethods.getUserTopics])[this.getTopicsKey]; if (transaction.returnData){
if (dataFetched){ this.postIDs = transaction.returnData;
this.setState({
'topicIDs': dataFetched.value,
'getTopicsTransactionState': "SUCCESS"
});
}
}
if (this.state.getPostsTransactionState === null){
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized
this.getPostsKey = this.drizzle.contracts[contract]
.methods[contractMethods.getUserPosts].cacheCall(this.state.userAddress);
this.setState({'getPostsTransactionState': "IN_PROGRESS"});
}
}
if (this.state.getPostsTransactionState === "IN_PROGRESS") {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState
.contracts[contract][contractMethods.getUserPosts])[this.getPostsKey];
if (dataFetched){
this.setState({
'postIDs': dataFetched.value,
'getPostsTransactionState': "SUCCESS"
});
} }
} }
} }
@ -203,14 +105,46 @@ Profile.contextTypes = {
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
accounts: state.accounts,
Forum: state.contracts.Forum,
user: state.user, user: state.user,
orbitDB: state.orbitDB, orbitDB: state.orbitDB
drizzleStatus: state.drizzleStatus
} }
}; };
const ProfileContainer = drizzleConnect(Profile, mapStateToProps); class ProfileContainer extends Component {
constructor(props){
super(props);
let userAddress;
if (this.props.params.address){
userAddress = this.props.params.address;
} else {
userAddress = this.props.user.address;
}
this.profile = <WithBlockchainData
component={drizzleConnect(Profile, mapStateToProps)}
callsInfo={[{
contract: 'Forum',
method: 'getUserTopics',
params: [userAddress]
},{
contract: 'Forum',
method: 'getUserPosts',
params: [userAddress]
}]}
params={this.props.params}
/>
}
render() {
return(this.profile);
}
}
const containerProps = state => {
return {
user: state.user
}
};
export default ProfileContainer; export default drizzleConnect(ProfileContainer, containerProps);

3
src/containers/StartTopicContainer.js

@ -235,8 +235,7 @@ class StartTopic extends Component {
'savingToOrbitDB': null, 'savingToOrbitDB': null,
'transactionOutputTimerActive': false 'transactionOutputTimerActive': false
}); });
this.props.router.push("/topic/" + this.topicIDFetched + "/" this.props.router.push("/topic/" + this.topicIDFetched);
+ this.state.topicSubjectInput);
}, 5000); }, 5000);
} }
else if (this.state.savingToOrbitDB === "ERROR"){ else if (this.state.savingToOrbitDB === "ERROR"){

69
src/containers/TopicContainer.js

@ -2,22 +2,16 @@ import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import WithBlockchainData from '../components/WithBlockchainData';
import PostList from '../components/PostList'; import PostList from '../components/PostList';
import NewPost from '../components/NewPost'; import NewPost from '../components/NewPost';
import FloatingButton from '../components/FloatingButton'; import FloatingButton from '../components/FloatingButton';
import LoadingSpinner from '../components/LoadingSpinner'; import LoadingSpinner from '../components/LoadingSpinner';
const contract = "Forum";
const contractMethod = "getTopic";
class Topic extends Component { class Topic extends Component {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
if (!/^[0-9]+$/.test(this.props.params.topicId)){
this.props.router.push("/404");
}
this.fetchTopicSubject = this.fetchTopicSubject.bind(this); this.fetchTopicSubject = this.fetchTopicSubject.bind(this);
this.handleClick = this.handleClick.bind(this); this.handleClick = this.handleClick.bind(this);
this.postCreated = this.postCreated.bind(this); this.postCreated = this.postCreated.bind(this);
@ -43,7 +37,7 @@ class Topic extends Component {
this.topicsSubjects[this.state.topicID] = som['subject']; this.topicsSubjects[this.state.topicID] = som['subject'];
this.topicsSubjectsFetchStatus[this.state.topicID] = "fetched";*/ this.topicsSubjectsFetchStatus[this.state.topicID] = "fetched";*/
var som =this.props.orbitDB.topicsDB.get(JSON.stringify(this.state.topicID)); var som =this.props.orbitDB.topicsDB.get(this.state.topicID);
this.setState({'topicSubject': som['subject']}); this.setState({'topicSubject': som['subject']});
} }
@ -79,7 +73,7 @@ class Topic extends Component {
onPostCreated={() => {this.postCreated()}} onPostCreated={() => {this.postCreated()}}
/> />
} }
<PostList postIDs={this.posts}/> {this.postList}
{!this.state.posting && {!this.state.posting &&
<FloatingButton onClick={this.handleClick}/> <FloatingButton onClick={this.handleClick}/>
} }
@ -95,25 +89,22 @@ class Topic extends Component {
} }
componentWillReceiveProps() { componentWillReceiveProps() {
if (this.state.getPostsTransactionState === null){ if (this.props.blockchainData[0].status === "success") {
if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized if (this.state.getPostsTransactionState !== "SUCCESS"){
//This gets called only once but should be called every time someone posts this.postList = <WithBlockchainData
this.getPostsDataKey = this.drizzle.contracts[contract].methods[contractMethod] component={PostList}
.cacheCall(this.state.topicID); callsInfo={this.props.blockchainData[0].returnData[4].map((postID) => {
this.setState({'getPostsTransactionState': "IN_PROGRESS"}); return {
} contract: 'Forum',
} method: 'getPost',
if (this.state.getPostsTransactionState === "IN_PROGRESS") { params: [postID]
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.getPostsDataKey];
if (dataFetched){
if (dataFetched.value){
this.posts = dataFetched.value[4];
this.setState({'getPostsTransactionState': "SUCCESS"});
this.fetchTopicSubject(dataFetched.value[0]);
} else if (dataFetched.error){
//TODO
} }
})}
postIDs={this.props.blockchainData[0].returnData[4]}
/>
this.setState({'getPostsTransactionState': "SUCCESS"});
this.fetchTopicSubject(this.props.blockchainData[0].returnData[0]);
} }
} }
} }
@ -130,6 +121,28 @@ const mapStateToProps = state => {
} }
}; };
const TopicContainer = drizzleConnect(Topic, mapStateToProps); class TopicContainer extends Component {
constructor(props){
super(props);
if (!/^[0-9]+$/.test(props.params.topicId)){ //Topic ID should be a positive integer
this.props.router.push("/404");
}
this.topic = <WithBlockchainData
component={drizzleConnect(Topic, mapStateToProps)}
callsInfo={[{
contract: 'Forum',
method: 'getTopic',
params: [this.props.params.topicId]
}]}
params={this.props.params}
/>;
}
render() {
return(this.topic);
}
}
export default TopicContainer; export default TopicContainer;

5
src/index.js

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router'; import { Router, IndexRoute, browserHistory } from 'react-router';
import { Route } from 'react-router-dom';
import { syncHistoryWithStore } from 'react-router-redux'; import { syncHistoryWithStore } from 'react-router-redux';
import { DrizzleProvider } from 'drizzle-react'; import { DrizzleProvider } from 'drizzle-react';
@ -31,7 +32,7 @@ render((
<Router history={history}> <Router history={history}>
<Route path="/" component={CoreLayout}> <Route path="/" component={CoreLayout}>
<IndexRoute component={HomeContainer} /> <IndexRoute component={HomeContainer} />
<Route path="/topic/:topicId(/:topicSubject)(/:postId)" <Route path="/topic/:topicId(/:postId)"
component={TopicContainer} /> component={TopicContainer} />
<Route path='/profile(/:address)(/:username)' <Route path='/profile(/:address)(/:username)'
component={ProfileContainer} /> component={ProfileContainer} />

Loading…
Cancel
Save