Browse Source

Add contract and OrbitDB interaction in NewPost, TopicContainer, PostList and ProfileInformation

develop
Apostolos Fanakis 7 years ago
parent
commit
6a595bc343
  1. 54
      contracts/Forum.sol
  2. 40
      src/assets/css/App.css
  3. 14
      src/components/LoadingSpinner.js
  4. 258
      src/components/NewPost.js
  5. 46
      src/components/Post.js
  6. 130
      src/components/PostList.js
  7. 9
      src/components/ProfileInformation.js
  8. 4
      src/components/Topic.js
  9. 51
      src/components/TopicList.js
  10. 20
      src/containers/BoardContainer.js
  11. 145
      src/containers/ProfileContainer.js
  12. 28
      src/containers/StartTopicContainer.js
  13. 90
      src/containers/TopicContainer.js
  14. 12
      src/helpers/EpochTimeConverter.js
  15. 2
      src/index.js
  16. 4
      src/util/drizzleOptions.js

54
contracts/Forum.sol

@ -55,6 +55,16 @@ contract Forum {
return false; return false;
} }
function getUserTopics(address userAddress) public view returns (uint[]) {
require (hasUserSignedUp(msg.sender), "User hasn't signed up yet.");
return users[userAddress].topicIDs;
}
function getUserPosts(address userAddress) public view returns (uint[]) {
require (hasUserSignedUp(msg.sender), "User hasn't signed up yet.");
return users[userAddress].postIDs;
}
//----------------------------------------OrbitDB---------------------------------------- //----------------------------------------OrbitDB----------------------------------------
struct OrbitDB { struct OrbitDB {
string id; // TODO: set an upper bound instead of arbitrary string string id; // TODO: set an upper bound instead of arbitrary string
@ -115,6 +125,7 @@ contract Forum {
uint postID; uint postID;
address author; address author;
uint timestamp; uint timestamp;
uint topicID;
} }
uint numTopics; // Total number of topics uint numTopics; // Total number of topics
@ -123,25 +134,33 @@ contract Forum {
mapping (uint => Topic) topics; mapping (uint => Topic) topics;
mapping (uint => Post) posts; mapping (uint => Post) posts;
event TopicCreated(uint topicID); event TopicCreated(uint topicID, uint postID);
event PostCreated(uint postID, uint topicID); event PostCreated(uint postID, uint topicID);
event NumberOfTopicsReceived(uint numTopics); /* event NumberOfTopicsReceived(uint numTopics);
event TopicReceived(string orbitTopicsDB, address author, string username, uint timestamp, uint[] postIDs); event TopicReceived(string orbitTopicsDB, address author, string username, uint timestamp, uint[] postIDs); */
function createTopic() public returns (uint) { function createTopic() public returns (uint, uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create topics require(hasUserSignedUp(msg.sender)); // Only registered users can create topics
//Creates topic
uint topicID = numTopics++; uint topicID = numTopics++;
topics[topicID] = Topic(topicID, msg.sender, block.timestamp, new uint[](0)); topics[topicID] = Topic(topicID, msg.sender, block.timestamp, new uint[](0));
users[msg.sender].topicIDs.push(topicID); users[msg.sender].topicIDs.push(topicID);
emit TopicCreated(topicID);
return topicID; //Adds first post to topic
uint postID = numPosts++;
posts[postID] = Post(postID, msg.sender, block.timestamp, topicID);
topics[topicID].postIDs.push(postID);
users[msg.sender].postIDs.push(postID);
emit TopicCreated(topicID, postID);
return (topicID, postID);
} }
function createPost(uint topicID) public returns (uint) { function createPost(uint topicID) public returns (uint) {
require(hasUserSignedUp(msg.sender)); // Only registered users can create posts require(hasUserSignedUp(msg.sender)); // Only registered users can create posts
require(topicID<numTopics); // Only allow posting to a topic that exists require(topicID<numTopics); // Only allow posting to a topic that exists
uint postID = numPosts++; uint postID = numPosts++;
posts[postID] = Post(postID, msg.sender, block.timestamp); posts[postID] = Post(postID, msg.sender, block.timestamp, topicID);
topics[topicID].postIDs.push(postID); topics[topicID].postIDs.push(postID);
users[msg.sender].postIDs.push(postID); users[msg.sender].postIDs.push(postID);
emit PostCreated(postID, topicID); emit PostCreated(postID, topicID);
@ -149,20 +168,14 @@ contract Forum {
} }
function getNumberOfTopics() public view returns (uint) { function getNumberOfTopics() public view returns (uint) {
emit NumberOfTopicsReceived(numTopics); /* emit NumberOfTopicsReceived(numTopics); */
return numTopics; return numTopics;
} }
function getTopic(uint topicID) public view returns (string, address, string, uint, uint[]) { function getTopic(uint topicID) public view returns (string, address, string, uint, uint[]) {
//require(hasUserSignedUp(msg.sender)); needed? //require(hasUserSignedUp(msg.sender)); needed?
require(topicID<numTopics); require(topicID<numTopics);
emit TopicReceived(getOrbitTopicsDB(topics[topicID].author), return (getOrbitTopicsDB(topics[topicID].author),
topics[topicID].author,
users[topics[topicID].author].username,
topics[topicID].timestamp,
topics[topicID].postIDs);
return (
getOrbitTopicsDB(topics[topicID].author),
topics[topicID].author, topics[topicID].author,
users[topics[topicID].author].username, users[topics[topicID].author].username,
topics[topicID].timestamp, topics[topicID].timestamp,
@ -174,4 +187,15 @@ contract Forum {
require(topicID<numTopics); // Topic should exist require(topicID<numTopics); // Topic should exist
return topics[topicID].postIDs; return topics[topicID].postIDs;
} }
function getPost(uint postID) public view returns (string, address, string, uint, uint) {
//require(hasUserSignedUp(msg.sender)); needed?
require(postID<numPosts);
return (getOrbitPostsDB(posts[postID].author),
posts[postID].author,
users[posts[postID].author].username,
posts[postID].timestamp,
posts[postID].topicID
);
}
} }

40
src/assets/css/App.css

@ -261,6 +261,40 @@ body,
padding: 7px; padding: 7px;
} }
/* PROFILE */
.profile-tabs-header {
width: 100%;
font-size: 1.72em;
font-weight: bold;
margin-bottom: 20px;
display: flex;
flex-flow: row nowrap;
justify-content: space-around;
}
.profile-tabs-header p {
width: 100%;
height: 100%;
text-align: center;
background-color: lightgrey;
padding: 10px;
margin: 0px;
cursor: pointer;
}
.profile-tabs-header p:hover {
background-color: grey;
}
.profile-tab-selected {
border-bottom: 2px solid #0c1a2b;
}
.profile-tab, .profile-tab>div {
width: 100%;
}
/* FORMS */ /* FORMS */
.pure-form { .pure-form {
@ -348,6 +382,12 @@ a {
text-align: center; text-align: center;
} }
.vertical-center-children {
display: flex;
flex-flow: row nowrap;
align-items: flex-start;
}
#overlay { #overlay {
position: fixed; position: fixed;
display: block; display: block;

14
src/components/LoadingSpinner.js

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

258
src/components/NewPost.js

@ -1,39 +1,98 @@
import React, { Component } from 'react';
import { drizzleConnect } from 'drizzle-react'; import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import uuidv4 from 'uuid/v4';
import UserAvatar from 'react-user-avatar'; import UserAvatar from 'react-user-avatar';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
const contract = "Forum";
const contractMethod = "createPost";
class NewPost extends Component { class NewPost 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.handleSubmit = this.handleSubmit.bind(this); this.validateAndPost = this.validateAndPost.bind(this);
this.pushToDatabase = this.pushToDatabase.bind(this);
this.transactionProgressText = [];
this.drizzle = context.drizzle;
this.state = { this.state = {
postContent: '', postSubjectInput: this.props.subject,
previewEnabled: false postContentInput: '',
postSubjectInputEmptySubmit: false,
postContentInputEmptySubmit: false,
previewEnabled: false,
previewDate: "",
creatingPost: false,
transactionState: null,
savingToOrbitDB: null,
transactionOutputTimerActive: false
}; };
} }
async handleSubmit() { async validateAndPost() {
/*this.stackId = this.contracts[contract].methods[startTopicMethod].cacheSend();*/ if (this.state.postSubjectInput === '' || this.state.postContentInput === ''){
this.setState({
postSubjectInputEmptySubmit: this.state.postSubjectInput === '',
postContentInputEmptySubmit: this.state.postContentInput === ''
});
return;
}
this.stackId = this.drizzle.contracts[contract].methods[contractMethod].cacheSend(this.props.topicID);
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push("Waiting for transaction acceptance...");
this.setState({
'creatingPost': true,
'transactionState': "ACCEPTANCE_PENDING"
});
}
async pushToDatabase() {
await this.props.orbitDB.postsDB.put(this.postIDFetched, {
subject: this.state.postSubjectInput,
content: this.state.postContentInput
});
this.setState({'savingToOrbitDB': "SUCCESS"});
} }
handleInputChange(event) { handleInputChange(event) {
this.setState({[event.target.name]: event.target.value}); this.setState({[event.target.name]: event.target.value});
} }
handlePreviewToggle(){ handlePreviewToggle() {
this.setState((prevState, props) => ({ this.setState((prevState, props) => ({
previewEnabled: !prevState.previewEnabled previewEnabled: !prevState.previewEnabled,
previewDate: this.getDate()
})); }));
} }
getDate() {
const currentdate = new Date();
return ((currentdate.getMonth() + 1) + " "
+ currentdate.getDate() + ", "
+ currentdate.getFullYear() + ", "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds());
}
render() { render() {
return ( return (
<div className="pure-u-1-1 post card"> <div className="pure-u-1-1 post card">
{this.state.creatingPost && <div id="overlay">
<div id="overlay-content">
<p><i className="fas fa-spinner fa-3x fa-spin"></i></p>
<br/>
{this.transactionProgressText}
</div>
</div>
}
<div className="post-header"> <div className="post-header">
<UserAvatar <UserAvatar
size="40" size="40"
@ -41,7 +100,7 @@ class NewPost extends Component {
src={this.props.user.avatarUrl} src={this.props.user.avatarUrl}
name={this.props.user.username}/> name={this.props.user.username}/>
<p className="inline no-margin"> <p className="inline no-margin">
<strong>{this.props.user.username}<br/>Subject: {this.props.subject}</strong> <strong>{this.props.user.username}<br/>Subject: {this.state.postSubjectInput}</strong>
</p> </p>
<div className="post-info"> <div className="post-info">
<span></span> <span></span>
@ -52,17 +111,27 @@ class NewPost extends Component {
<div className="post-content"> <div className="post-content">
<form className="topic-form"> <form className="topic-form">
{this.state.previewEnabled {this.state.previewEnabled
? <ReactMarkdown source={this.state.postContent} className="markdownPreview" /> ? <ReactMarkdown source={this.state.postContentInput} className="markdownPreview" />
: <textarea key={"postContent"} : [
name={"postContent"} <input key={"postSubjectInput"}
value={this.state.postContent} name={"postSubjectInput"}
className={this.state.postSubjectInputEmptySubmit ? "form-input-required" : ""}
type="text"
value={this.state.postSubjectInput}
placeholder="Subject"
id="postSubjectInput"
onChange={this.handleInputChange} />,
<textarea key={"postContentInput"}
name={"postContentInput"}
value={this.state.postContentInput}
placeholder="Post" placeholder="Post"
id="postContent" id="postContentInput"
onChange={this.handleInputChange} />} onChange={this.handleInputChange} />
]}
<button key="submit" <button key="submit"
className="pure-button pure-button-primary" className="pure-button pure-button-primary"
type="button" type="button"
onClick={this.handleSubmit}> onClick={this.validateAndPost}>
Post Post
</button> </button>
<button className="pure-button margin-left-small" <button className="pure-button margin-left-small"
@ -80,10 +149,167 @@ class NewPost extends Component {
</div> </div>
); );
} }
componentWillReceiveProps(){
if(this.state.creatingPost && !this.state.transactionOutputTimerActive){
/* User submitted a new Post */
if (this.state.transactionState === "ACCEPTANCE_PENDING" &&
this.props.transactionStack[this.stackId]) {
/* User confirmed the transaction */
//Gets transaciton's hash
this.txHash = this.props.transactionStack[this.stackId];
//Updates output and state
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push("Transaction in progress: txHash = " + this.txHash);
this.setState({'transactionState': "IN_PROGRESS"});
}
else if (this.state.transactionState === "IN_PROGRESS") {
if (this.props.transactions[this.txHash].status === "success"){
/* Transaction completed successfully */
//Gets post's id returned by contract
let postData = this.props.transactions[this.txHash].receipt.events.PostCreated
.returnValues;
this.topicIDFetched = postData.topicID;
this.postIDFetched = postData.postID;
//Updates output and state
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}>
<strong>
Transaction completed successfully.
</strong>
</span>);
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}>
<strong>
TopicID = {this.topicIDFetched}, PostID = {this.postIDFetched}
</strong>
</span>);
this.setState({'transactionState': "SUCCESS"});
} else if (this.props.transactions[this.txHash].status === "error"){
/* Transaction failed to complete */
//Updates output and state
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'red'}}>
<strong>
Transaction failed to complete with error:
</strong>
</span>);
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'red'}}>
<strong>
{this.props.transactions[this.txHash].error}
</strong>
</span>);
this.setState({
'transactionState': "ERROR",
'transactionOutputTimerActive': true
});
this.transactionOutputTimer = setTimeout(() => {
this.transactionProgressText = [];
this.setState({
'creatingPost': false,
'transactionState': null,
'savingToOrbitDB': null,
'transactionOutputTimerActive': false
});
this.props.onPostCreated();
}, 5000);
}
}
else if (this.state.transactionState === "SUCCESS") {
/* Transaction completed successfully */
//Tries to store data in OrbitDB
this.pushToDatabase();
if (this.state.savingToOrbitDB === "SUCCESS"){
/* Data successfully saved in OrbitDB */
//Updates output and state
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}>
<strong>
Post successfully saved in OrbitDB.
</strong>
</span>);
this.setState({'transactionOutputTimerActive': true});
this.transactionOutputTimer = setTimeout(() => {
this.transactionProgressText = [];
this.setState({
'creatingPost': false,
'transactionState': null,
'savingToOrbitDB': null,
'transactionOutputTimerActive': false
});
this.props.onPostCreated();
}, 5000);
}
else if (this.state.savingToOrbitDB === "ERROR"){
/* Failed to save data in OrbitDB */
//Updates output and state
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'red'}}>
<strong>
An error occurred while trying to save post in OrbitDB.
</strong>
</span>);
this.setState({'transactionOutputTimerActive': true});
this.transactionOutputTimer = setTimeout(() => {
this.transactionProgressText = [];
this.setState({
'creatingPost': false,
'transactionState': null,
'savingToOrbitDB': null,
'transactionOutputTimerActive': false
});
this.props.onPostCreated();
}, 5000);
}
}
else if (this.state.transactionState === "ACCEPTANCE_PENDING" &&
this.props.transactions.undefined !== undefined &&
this.props.transactions.undefined.status === "error"){
/* User probably canceled the transaction */
//TODO user can't post after this!
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'orange'}}>
<strong>
Transaction canceled.
</strong>
</span>);
this.setState({'transactionState': "SUCCESS"});
this.setState({'transactionOutputTimerActive': true});
this.transactionOutputTimer = setTimeout(() => {
this.transactionProgressText = [];
this.setState({
'creatingPost': false,
'transactionState': null,
'savingToOrbitDB': null,
'transactionOutputTimerActive': false
});
this.props.onPostCreated();
}, 5000);
}
}
}
}
NewPost.contextTypes = {
drizzle: PropTypes.object
}; };
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
transactions: state.transactions,
transactionStack: state.transactionStack,
orbitDB: state.orbitDB,
user: state.user user: state.user
} }
}; };

46
src/components/Post.js

@ -4,27 +4,53 @@ import TimeAgo from 'react-timeago';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
const Post = (props) => { const Post = (props) => {
const username = props.username && [props.username, <br key={props.id}/>];
return ( return (
<div className="pure-u-1-1 post card"> props.post !== null
? <div className="pure-u-1-1 post card">
<div className="post-header"> <div className="post-header">
{props.avatarUrl && <UserAvatar <div className="vertical-center-children">
<UserAvatar
size="40" size="40"
className="inline user-avatar" className="inline user-avatar"
src={props.avatarUrl} src={props.post.avatarUrl}
name={props.username}/>} name={props.post.username}/>
<p className="inline no-margin">
<strong>
{props.post.username}
<br/>
Subject: {props.post.subject}
</strong>
</p>
</div>
<div className="post-info">
<span>Posted <TimeAgo date={props.post.date}/></span>
<span>#{props.post.postIndex}</span>
</div>
</div>
<hr/>
<div className="post-content">
{props.post.postContent
? <ReactMarkdown source={props.post.postContent} />
: <p style={{color: 'grey'}}>Post content...</p>
}
</div>
<hr/>
<div className="post-meta">
Maybe add buttons for upvote etc here...
</div>
</div>
: <div className="pure-u-1-1 post card" style={{color: 'grey'}}>
<div className="post-header">
<p className="inline no-margin"> <p className="inline no-margin">
<strong>{username}Subject: {props.subject}</strong> <strong>Subject</strong>
</p> </p>
<div className="post-info"> <div className="post-info">
<span>Posted <TimeAgo date={props.date}/></span> <span>Posted </span>
{props.postIndex && <span>#{props.postIndex}</span>}
</div> </div>
</div> </div>
<hr/> <hr/>
<div className="post-content"> <div className="post-content">
<ReactMarkdown source={props.postContent} /> <p>Post content...</p>
</div> </div>
<hr/> <hr/>
<div className="post-meta"> <div className="post-meta">

130
src/components/PostList.js

@ -1,45 +1,107 @@
import React from 'react'; import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react';
import { Link } from 'react-router';
import PropTypes from 'prop-types';
import Post from './Post'; import Post from './Post';
const posts1 = [ import epochTimeConverter from '../helpers/EpochTimeConverter'
{avatarUrl: "https://i.makeagif.com/media/4-18-2018/8BLiwJ.gif",
username: "Apostolof", const contract = "Forum";
subject: "Some very important topic of discussion2!", const contractMethod = "getPost";
date: "May 25, 2018, 11:11:11",
postIndex: "1", class PostList extends Component {
postContent: "# We have markdown!!!\n \n**Oh yes we do!!** \n*ITALICS* \n \n```Some code```", constructor(props, context) {
id: 2, super(props);
address: 0x083c41ea13af6c2d5aaddf6e73142eb9a7b00183
}, this.fetchPost = this.fetchPost.bind(this);
{avatarUrl: "",
username: "", this.drizzle = context.drizzle;
subject: "Some very important topic of discussion!", this.dataKeys = [];
date: "May 20, 2018, 10:10:10", this.postsData = new Array(parseInt(this.props.postIDs.length, 10)).fill(undefined);
postIndex: "", this.orbitPostsData = new Array(parseInt(this.props.postIDs.length, 10)).fill(undefined);
postContent: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur, natus ipsum minima.", this.orbitPostsDataFetchStatus = new Array(parseInt(this.props.postIDs.length, 10)).fill("pending");
id: 1,
address: 0x5fe3062B24033113fbf52b2b75882890D7d8CA54 for (var i = 0; i < this.props.postIDs.length; ++i){
} this.dataKeys[i] = this.drizzle.contracts[contract].methods[contractMethod]
]; .cacheCall(this.props.postIDs[i]);
}
const PostList = (props) => { }
const posts = posts1.map((post) =>
<Post avatarUrl={post.avatarUrl} async fetchPost(index) {
username={post.username} /*const fullAddress = this.postsData[postID][1];
subject={post.subject} const store = await this.props.orbitDB.orbitdb.keyvalue(JSON.stringify(fullAddress));
date={post.date} await store.load();
postIndex={post.postIndex} var som = store.get(JSON.stringify(postID));
postContent={post.postContent} this.orbitPostsData[postID] = som['subject'];
id={post.id} this.orbitPostsDataFetchStatus[postID] = "fetched";*/
key={post.id}
address={post.address}/> var som = this.props.orbitDB.postsDB.get(this.props.postIDs[index]);
this.orbitPostsData[index] = som;
this.orbitPostsDataFetchStatus[index] = "fetched";
}
render (){
const posts = this.postsData.map((post, index) => {
if (post) {
return (
<Link to={"/topic/" + post[4] + "/" +
((this.orbitPostsData[index] !== undefined) ? this.orbitPostsData[index].subject + "/" +
this.props.postIDs[index] : "")}
key={index}>
<Post post={{
avatarUrl: post.avatarUrl,
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}/>
</Link>
); );
} else {
return (
<Post post={null}
id={index}
key={index}/>
);
}
});
return ( return (
<div className="posts-list"> <div className="posts-list">
{posts} {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
};
const mapStateToProps = state => {
return {
user: state.user, //Needed!!
orbitDB: state.orbitDB,
}
}; };
export default PostList; export default drizzleConnect(PostList, mapStateToProps);

9
src/components/ProfileInformation.js

@ -11,7 +11,7 @@ const ProfileInformation = (props) => {
src={props.avatarUrl} src={props.avatarUrl}
name={props.username}/>} name={props.username}/>}
<p className="no-margin inline"> <p className="no-margin inline">
<strong>Username</strong>: {props.username} <strong>Username:</strong> {props.username}
</p> </p>
<p className="no-margin"> <p className="no-margin">
<strong>Account address:</strong> {props.address} <strong>Account address:</strong> {props.address}
@ -20,9 +20,12 @@ const ProfileInformation = (props) => {
<strong>OrbitDB:</strong> {props.orbitAddress} <strong>OrbitDB:</strong> {props.orbitAddress}
</p> </p>
<p className="no-margin"> <p className="no-margin">
Number of posts: TODO? <strong>Number of topics created:</strong> {props.numberOfTopics}
</p> </p>
<UsernameFormContainer/> <p className="no-margin">
<strong>Number of posts:</strong> {props.numberOfPosts}
</p>
{props.self && <UsernameFormContainer/>}
</div> </div>
); );
}; };

4
src/components/Topic.js

@ -4,7 +4,7 @@ import TimeAgo from 'react-timeago';
const Topic = (props) => { const Topic = (props) => {
return ( return (
props.topic !== null props.topic !== null
? <div className={"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: props.topic.topicSubject ? "" : "grey"}}>
<strong>{props.topic.topicSubject ? props.topic.topicSubject : "Subject"}</strong> <strong>{props.topic.topicSubject ? props.topic.topicSubject : "Subject"}</strong>
</p> </p>
@ -15,7 +15,7 @@ const Topic = (props) => {
<p className="topic-date">Started <TimeAgo date={props.topic.date}/></p> <p className="topic-date">Started <TimeAgo date={props.topic.date}/></p>
</div> </div>
</div> </div>
: <div className={"topic card"} style={{color: 'grey'}}> : <div className={"pure-u-1-1 topic card"} style={{color: 'grey'}}>
<p className="topic-subject"><strong>Subject</strong></p> <p className="topic-subject"><strong>Subject</strong></p>
<hr/> <hr/>
<div className="topic-meta"> <div className="topic-meta">

51
src/components/TopicList.js

@ -1,10 +1,12 @@
import { drizzleConnect } from 'drizzle-react'; import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import PropTypes from 'prop-types' import PropTypes from 'prop-types';
import Topic from './Topic'; import Topic from './Topic';
import epochTimeConverter from '../helpers/EpochTimeConverter'
const contract = "Forum"; const contract = "Forum";
const contractMethod = "getTopic"; const contractMethod = "getTopic";
@ -13,62 +15,50 @@ class TopicList extends Component {
super(props); super(props);
this.fetchSubject = this.fetchSubject.bind(this); this.fetchSubject = this.fetchSubject.bind(this);
this.correctTimeFormat = this.correctTimeFormat.bind(this);
this.drizzle = context.drizzle; this.drizzle = context.drizzle;
this.dataKeys = []; this.dataKeys = [];
this.topicsData = new Array(parseInt(this.props.numberOfTopics, 10)).fill(undefined); this.topicsData = new Array(parseInt(this.props.topicIDs.length, 10)).fill(undefined);
this.topicsSubjects = []; this.topicsSubjects = [];
this.topicsSubjectsFetched = []; this.topicsSubjectsFetchStatus = new Array(parseInt(this.props.topicIDs.length, 10)).fill("pending");
for (var i = 0; i < this.props.numberOfTopics; ++i){ for (var i = 0; i < this.props.topicIDs.length; ++i){
this.dataKeys[i] = this.drizzle.contracts[contract].methods[contractMethod].cacheCall(i); this.dataKeys[i] = this.drizzle.contracts[contract].methods[contractMethod]
.cacheCall(this.props.topicIDs[i]);
} }
this.state = { this.state = {
}; };
} }
async fetchSubject(topicID) { async fetchSubject(topicIndex) {
/*const fullAddress = this.topicsData[topicID][1]; /*const fullAddress = this.topicsData[topicID][1];
const store = await this.props.orbitDB.orbitdb.keyvalue(JSON.stringify(fullAddress)); const store = await this.props.orbitDB.orbitdb.keyvalue(JSON.stringify(fullAddress));
await store.load(); await store.load();
var som = store.get(JSON.stringify(topicID)); var som = store.get(JSON.stringify(topicID));
this.topicsSubjects[topicID] = som['subject']; this.topicsSubjects[topicID] = som['subject'];
this.topicsSubjectsFetched[topicID] = true;*/ this.topicsSubjectsFetchStatus[topicID] = "fetched";*/
var som =this.props.orbitDB.topicsDB.get(JSON.stringify(topicID)); var som =this.props.orbitDB.topicsDB.get(this.props.topicIDs[topicIndex]);
this.topicsSubjects[topicID] = som['subject']; this.topicsSubjects[topicIndex] = som['subject'];
this.topicsSubjectsFetched[topicID] = true; this.topicsSubjectsFetchStatus[topicIndex] = "fetched";
}
correctTimeFormat(timestamp) {
var timestampDate = new Date(0);
timestampDate.setUTCSeconds(timestamp);
return ((timestampDate.getMonth() + 1) + " "
+ timestampDate.getDate() + ", "
+ timestampDate.getFullYear() + ", "
+ timestampDate.getHours() + ":"
+ timestampDate.getMinutes() + ":"
+ timestampDate.getSeconds())
} }
render (){ render (){
const topics = this.topicsData.slice(0).reverse().map((topic, index) => { const topics = this.topicsData.map((topic, index) => {
if (topic){ if (topic){
return ( return (
<Link to={"/topic/" + index + "/" + <Link to={"/topic/" + index + "/" +
((this.topicsSubjects[index] !== undefined) ? this.topicsSubjects[index] : "")} ((this.topicsSubjects[index] !== undefined) ? this.topicsSubjects[index] + "/" + 0 : "")}
key={index}> key={index}>
<Topic topic={{ <Topic topic={{
topicSubject: ((this.topicsSubjects[index] !== undefined) && this.topicsSubjects[index]), topicSubject: ((this.topicsSubjects[index] !== undefined) && this.topicsSubjects[index]),
topicStarter: topic[2], topicStarter: topic[2],
numberOfReplies: topic[4].length, numberOfReplies: topic[4].length,
date: this.correctTimeFormat(topic[3]) date: epochTimeConverter(topic[3])
}} }}
id={index} id={index}
key={index} key={index}/>
address={topic[1]}/>
</Link> </Link>
); );
} else { } else {
@ -85,20 +75,21 @@ class TopicList extends Component {
return ( return (
<div className="topics-list"> <div className="topics-list">
{topics} {topics.slice(0).reverse()}
</div> </div>
); );
} }
componentWillReceiveProps() { componentWillReceiveProps() {
for (var i = 0; i < this.props.numberOfTopics; ++i){ for (var i = 0; i < this.props.topicIDs.length; ++i){
if (this.topicsData[i] === undefined) { if (this.topicsData[i] === undefined) {
let currentDrizzleState = this.drizzle.store.getState(); let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKeys[i]]; let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKeys[i]];
if (dataFetched){ if (dataFetched){
this.topicsData[i] = dataFetched.value; this.topicsData[i] = dataFetched.value;
} }
} else if (!this.topicsSubjects[i] && !this.topicsSubjectsFetched[i]) { } else if (!this.topicsSubjects[i] && this.topicsSubjectsFetchStatus[i] === "pending") {
this.topicsSubjectsFetchStatus[i] = "fetching";
this.fetchSubject(i); this.fetchSubject(i);
} }
} }

20
src/containers/BoardContainer.js

@ -5,6 +5,7 @@ import { Link } from 'react-router';
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';
const contract = "Forum"; const contract = "Forum";
const contractMethod = "getNumberOfTopics"; const contractMethod = "getNumberOfTopics";
@ -16,7 +17,6 @@ class Board extends Component {
this.drizzle = context.drizzle; this.drizzle = context.drizzle;
this.state = { this.state = {
startingNewTopic: false,
transactionState: null transactionState: null
}; };
} }
@ -25,18 +25,14 @@ class Board extends Component {
var boardContents; var boardContents;
if (this.state.transactionState !== "SUCCESS") { if (this.state.transactionState !== "SUCCESS") {
boardContents = ( boardContents = (
<div className="center-in-parent"> <LoadingSpinner/>
<p>
<i className="fas fa-spinner fa-3x fa-spin"></i>
</p>
</div>
); );
} else { } else {
boardContents = <TopicList numberOfTopics={this.numberOfTopics}/>; boardContents = <TopicList topicIDs={this.topicIDs}/>;
} }
return ( return (
<div style={{marginBottom: '100px'}}> <div style={{marginBottom: '70px'}}>
{boardContents} {boardContents}
<Link to="/startTopic"> <Link to="/startTopic">
<FloatingButton onClick={this.handleClick}/> <FloatingButton onClick={this.handleClick}/>
@ -47,7 +43,7 @@ class Board extends Component {
componentWillReceiveProps() { componentWillReceiveProps() {
if (this.state.transactionState === null){ if (this.state.transactionState === null){
if (this.drizzle.contracts[contract]){ if (this.drizzle.contracts[contract]){ //Waits until drizzle is initialized
//This gets called only once but should be called every time someone posts //This gets called only once but should be called every time someone posts
this.dataKey = this.drizzle.contracts[contract].methods[contractMethod].cacheCall(); this.dataKey = this.drizzle.contracts[contract].methods[contractMethod].cacheCall();
this.setState({'transactionState': "IN_PROGRESS"}); this.setState({'transactionState': "IN_PROGRESS"});
@ -57,7 +53,11 @@ class Board extends Component {
let currentDrizzleState = this.drizzle.store.getState(); let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKey]; let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.dataKey];
if (dataFetched){ if (dataFetched){
this.numberOfTopics = dataFetched.value this.numberOfTopics = dataFetched.value;
this.topicIDs = [];
for (var i = 0; i < this.numberOfTopics; i++) {
this.topicIDs.push(i);
}
this.setState({'transactionState': "SUCCESS"}); this.setState({'transactionState': "SUCCESS"});
} }
} }

145
src/containers/ProfileContainer.js

@ -1,25 +1,154 @@
import { drizzleConnect } from 'drizzle-react'; import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ProfileInformation from '../components/ProfileInformation'; import ProfileInformation from '../components/ProfileInformation';
import TopicList from '../components/TopicList';
import PostList from '../components/PostList'; import PostList from '../components/PostList';
import LoadingSpinner from '../components/LoadingSpinner';
const contract = "Forum";
const contractMethods = {
getOrbitDB: "getOrbitDBId",
getUserTopics: "getUserTopics",
getUserPosts: "getUserPosts"
};
class Profile extends Component {
constructor(props, context) {
super(props);
//THIS WILL CHANGE WITH ACTUAL DATA
this.match = {
username: this.props.user.username,
userAddress: this.props.user.address
}
this.handleTabClick = this.handleTabClick.bind(this);
this.drizzle = context.drizzle;
this.state = {
viewSelected: "topics",
username: this.match.username, // TODO actually get them from match
userAddress: this.match.address, // when router is fixed
orbitDBId: this.match.address === this.props.user.address ? this.props.orbitDB.id : "",
getOrbitDBTransactionState: this.match.address === this.props.user.address ? "SUCCESS" : null,
getTopicsTransactionState: null,
getPostsTransactionState: null,
topicIDs: [],
postIDs: []
};
}
handleTabClick(id) {
this.setState({viewSelected: id});
}
class SignUp extends Component {
render() { render() {
return ( return (
<div className="pure-g"> <div className="pure-g">
<ProfileInformation username={this.props.user.username} <ProfileInformation username={this.state.username}
address={this.props.user.address} address={this.state.userAddress}
orbitAddress={this.props.orbitDB.id}/> orbitAddress={this.state.orbitDBId}
<p className="pure-u-1-1"> numberOfTopics={this.state.topicIDs.length}
My posts: numberOfPosts={this.state.postIDs.length}
self/>
<div className="pure-u-1-1 profile-tabs-header">
<p onClick={() => (this.handleTabClick("topics"))}
className={this.state.viewSelected === "topics" ? "profile-tab-selected" : ""}>
Topics
</p>
<p onClick={() => (this.handleTabClick("posts"))}
className={this.state.viewSelected === "posts" ? "profile-tab-selected" : ""}>
Posts
</p> </p>
<PostList/> {/*TODO change this with actual user's posts*/} </div>
{this.state.viewSelected === "topics"
?<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() {
if (this.state.getOrbitDBTransactionState === 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.orbitDBIdKey = this.drizzle.contracts[contract]
.methods[contractMethods.getOrbitDB].cacheCall(this.match.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 gets called only once but should be called every time someone posts
this.getTopicsKey = this.drizzle.contracts[contract]
.methods[contractMethods.getUserTopics].cacheCall(this.match.userAddress);
this.setState({'getTopicsTransactionState': "IN_PROGRESS"});
}
}
if (this.state.getTopicsTransactionState === "IN_PROGRESS") {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState
.contracts[contract][contractMethods.getUserTopics])[this.getTopicsKey];
if (dataFetched){
this.setState({
'topicIDs': dataFetched.value,
'getTopicsTransactionState': "SUCCESS"
});
}
}
if (this.state.getPostsTransactionState === 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.getPostsKey = this.drizzle.contracts[contract]
.methods[contractMethods.getUserPosts].cacheCall(this.match.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"
});
}
}
}
} }
Profile.contextTypes = {
drizzle: PropTypes.object
};
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
accounts: state.accounts, accounts: state.accounts,
@ -30,6 +159,6 @@ const mapStateToProps = state => {
} }
}; };
const ProfileContainer = drizzleConnect(SignUp, mapStateToProps); const ProfileContainer = drizzleConnect(Profile, mapStateToProps);
export default ProfileContainer; export default ProfileContainer;

28
src/containers/StartTopicContainer.js

@ -54,6 +54,10 @@ class StartTopic extends Component {
async pushToDatabase() { async pushToDatabase() {
await this.props.orbitDB.topicsDB.put(this.topicIDFetched, { await this.props.orbitDB.topicsDB.put(this.topicIDFetched, {
subject: this.state.topicSubjectInput
});
await this.props.orbitDB.postsDB.put(this.postIDFetched, {
subject: this.state.topicSubjectInput, subject: this.state.topicSubjectInput,
content: this.state.topicMessageInput content: this.state.topicMessageInput
}); });
@ -82,7 +86,7 @@ class StartTopic extends Component {
} }
render() { render() {
return( return (
<div> <div>
{this.state.creatingTopic && <div id="overlay"> {this.state.creatingTopic && <div id="overlay">
<div id="overlay-content"> <div id="overlay-content">
@ -93,11 +97,13 @@ class StartTopic extends Component {
</div> </div>
} }
{this.state.previewEnabled && {this.state.previewEnabled &&
<Post avatarUrl={this.props.user.avatarUrl} <Post post = {{
username={this.props.user.username} avatarUrl: this.props.user.avatarUrl,
subject={this.state.topicSubjectInput} username: this.props.user.username,
date={this.state.previewDate} subject: this.state.topicSubjectInput,
postContent={this.state.topicMessageInput} date: this.state.previewDate,
postContent: this.state.topicMessageInput
}}
id={0}/>} id={0}/>}
<form className="topic-form"> <form className="topic-form">
{!this.state.previewEnabled && {!this.state.previewEnabled &&
@ -155,8 +161,10 @@ class StartTopic extends Component {
/* Transaction completed successfully */ /* Transaction completed successfully */
//Gets topic's id returned by contract //Gets topic's id returned by contract
this.topicIDFetched = this.props.transactions[this.txHash].receipt let topicData = this.props.transactions[this.txHash].receipt.events.TopicCreated
.events.TopicCreated.returnValues.topicID; .returnValues;
this.topicIDFetched = topicData.topicID;
this.postIDFetched = topicData.postID;
//Updates output and state //Updates output and state
this.transactionProgressText.push(<br key={uuidv4()}/>); this.transactionProgressText.push(<br key={uuidv4()}/>);
@ -168,7 +176,7 @@ class StartTopic extends Component {
this.transactionProgressText.push(<br key={uuidv4()}/>); this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}> this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}>
<strong> <strong>
TopicID = {this.topicIDFetched} TopicID = {this.topicIDFetched}, PostID = {this.postIDFetched}
</strong> </strong>
</span>); </span>);
this.setState({'transactionState': "SUCCESS"}); this.setState({'transactionState': "SUCCESS"});
@ -215,7 +223,7 @@ class StartTopic extends Component {
this.transactionProgressText.push(<br key={uuidv4()}/>); this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}> this.transactionProgressText.push(<span key={uuidv4()} style={{color: 'green'}}>
<strong> <strong>
Post successfully saved in OrbitDB. Topic successfully saved in OrbitDB.
</strong> </strong>
</span>); </span>);
this.setState({'transactionOutputTimerActive': true}); this.setState({'transactionOutputTimerActive': true});

90
src/containers/TopicContainer.js

@ -1,18 +1,42 @@
import { drizzleConnect } from 'drizzle-react'; import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
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';
const contract = "Forum";
const contractMethod = "getTopic";
class Topic extends Component { class Topic extends Component {
constructor(props) { constructor(props, context) {
super(props); super(props);
this.fetchTopicSubject = this.fetchTopicSubject.bind(this);
this.handleClick = this.handleClick.bind(this);
this.postCreated = this.postCreated.bind(this);
this.drizzle = context.drizzle;
this.state = { this.state = {
posting: false getPostsTransactionState: null,
posting: false,
topicSubject: null
}; };
}
this.handleClick = this.handleClick.bind(this); async fetchTopicSubject(orbitDBAddress, topicID) {
/*const fullAddress = this.topicsData[topicID][1];
const store = await this.props.orbitDB.orbitdb.keyvalue(JSON.stringify(fullAddress));
await store.load();
var som = store.get(JSON.stringify(topicID));
this.topicsSubjects[topicID] = som['subject'];
this.topicsSubjectsFetchStatus[topicID] = "fetched";*/
var som =this.props.orbitDB.topicsDB.get(JSON.stringify(topicID));
this.setState({'topicSubject': som['subject']});
} }
handleClick(event) { handleClick(event) {
@ -24,28 +48,72 @@ class Topic extends Component {
})); }));
} }
postCreated(){
this.setState(prevState => ({
getPostsTransactionState: null,
posting: false
}));
}
render() { render() {
return ( var topicContents;
if (this.state.getPostsTransactionState !== "SUCCESS") {
topicContents = (
<LoadingSpinner/>
);
} else {
topicContents = (
this.state.posting this.state.posting
?(<div style={{marginBottom: '100px'}}> ?(<div style={{marginBottom: '100px'}}>
<PostList/> <NewPost topicID={1}
<NewPost onCancelClick={() => {this.handleClick()}}/> subject={this.state.topicSubject}
onCancelClick={() => {this.handleClick()}}
onPostCreated={() => {this.postCreated()}}
/>
<PostList postIDs={this.posts}/>
</div>) </div>)
:(<div style={{marginBottom: '100px'}}> :(<div style={{marginBottom: '100px'}}>
<PostList/> <PostList postIDs={this.posts}/>
<FloatingButton onClick={this.handleClick}/> <FloatingButton onClick={this.handleClick}/>
</div>) </div>)
)
}
return (
<div style={{marginBottom: '70px'}}>
{topicContents}
</div>
); );
} }
componentWillReceiveProps() {
if (this.state.getPostsTransactionState === 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.getPostsDataKey = this.drizzle.contracts[contract].methods[contractMethod].cacheCall(1);
this.setState({'getPostsTransactionState': "IN_PROGRESS"});
}
}
if (this.state.getPostsTransactionState === "IN_PROGRESS") {
let currentDrizzleState = this.drizzle.store.getState();
let dataFetched = (currentDrizzleState.contracts[contract][contractMethod])[this.getPostsDataKey];
if (dataFetched){
this.posts = dataFetched.value[4];
this.setState({'getPostsTransactionState': "SUCCESS"});
this.fetchTopicSubject(dataFetched.value[0], 1);
}
}
}
} }
Topic.contextTypes = {
drizzle: PropTypes.object
};
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
} }
}; };

12
src/helpers/EpochTimeConverter.js

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

2
src/index.js

@ -31,7 +31,7 @@ render((
<Router history={history}> <Router history={history}>
<Route path="/" component={CoreLayout}> <Route path="/" component={CoreLayout}>
<IndexRoute component={HomeContainer} /> <IndexRoute component={HomeContainer} />
<PrivateRouteContainer path="/topic/:topicId/:topicSubject" component={TopicContainer} redirectTo="/" /> <PrivateRouteContainer path="/topic/:topicId/:topicSubject/:postId" component={TopicContainer} redirectTo="/" />
<PrivateRouteContainer path='/profile' component={ProfileContainer} redirectTo="/" /> <PrivateRouteContainer path='/profile' component={ProfileContainer} redirectTo="/" />
<PrivateRouteContainer path='/startTopic' component={StartTopicContainer} redirectTo="/" /> <PrivateRouteContainer path='/startTopic' component={StartTopicContainer} redirectTo="/" />
<Route path='/404' component={NotFoundView} /> <Route path='/404' component={NotFoundView} />

4
src/util/drizzleOptions.js

@ -14,9 +14,7 @@ const drizzleOptions = {
Forum: ['UserSignedUp', Forum: ['UserSignedUp',
'UsernameUpdated', 'UsernameUpdated',
'TopicCreated', 'TopicCreated',
'PostCreated', 'PostCreated']
'NumberOfTopicsReceived',
'TopicReceived']
}, },
polls: { polls: {
accounts: 3000, accounts: 3000,

Loading…
Cancel
Save