diff --git a/contracts/Forum.sol b/contracts/Forum.sol index e96b908..7d9c5e3 100644 --- a/contracts/Forum.sol +++ b/contracts/Forum.sol @@ -8,6 +8,7 @@ contract Forum { OrbitDB orbitdb; uint[] topicIDs; // IDs of the topics the user created uint[] postIDs; // IDs of the posts the user created + uint timestamp; bool signedUp; // Helper variable for hasUserSignedUp() } @@ -20,7 +21,9 @@ contract Forum { function signUp(string username, string orbitDBId, string orbitTopicsDB, string orbitPostsDB, string orbitPublicKey, string orbitPrivateKey) public returns (bool) { require (!hasUserSignedUp(msg.sender), "User has already signed up."); require(!isUserNameTaken(username), "Username is already taken."); - users[msg.sender] = User(username, OrbitDB(orbitDBId, orbitTopicsDB, orbitPostsDB, orbitPublicKey, orbitPrivateKey), new uint[](0), new uint[](0), true); + users[msg.sender] = User(username, + OrbitDB(orbitDBId,orbitTopicsDB, orbitPostsDB, orbitPublicKey, orbitPrivateKey), + new uint[](0), new uint[](0), block.timestamp, true); userAddresses[username] = msg.sender; emit UserSignedUp(username, msg.sender); return true; @@ -56,15 +59,20 @@ contract Forum { } function getUserTopics(address userAddress) public view returns (uint[]) { - require (hasUserSignedUp(msg.sender), "User hasn't signed up yet."); + require (hasUserSignedUp(userAddress), "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."); + require (hasUserSignedUp(userAddress), "User hasn't signed up yet."); return users[userAddress].postIDs; } + function getUserDateOfRegister(address userAddress) public view returns (uint) { + require (hasUserSignedUp(userAddress), "User hasn't signed up yet."); + return users[userAddress].timestamp; + } + //----------------------------------------OrbitDB---------------------------------------- struct OrbitDB { string id; // TODO: set an upper bound instead of arbitrary string @@ -136,8 +144,6 @@ contract Forum { event TopicCreated(uint topicID, uint postID); event PostCreated(uint postID, uint topicID); - /* event NumberOfTopicsReceived(uint numTopics); - event TopicReceived(string orbitTopicsDB, address author, string username, uint timestamp, uint[] postIDs); */ function createTopic() public returns (uint, uint) { require(hasUserSignedUp(msg.sender)); // Only registered users can create topics @@ -168,7 +174,6 @@ contract Forum { } function getNumberOfTopics() public view returns (uint) { - /* emit NumberOfTopicsReceived(numTopics); */ return numTopics; } diff --git a/src/assets/css/App.css b/src/assets/css/App.css index 1ff9452..c7946cf 100644 --- a/src/assets/css/App.css +++ b/src/assets/css/App.css @@ -184,15 +184,15 @@ body, /* FLOATING BUTTONS */ .floating-button{ - position:fixed; - width:60px; - height:60px; - bottom:40px; - right:40px; - background-color:#0C9; - color:#FFF; - border-radius:50px; - text-align:center; + position: fixed; + width: 60px; + height: 60px; + bottom: 40px; + right: 40px; + background-color: #0C9; + color: #FFF; + border-radius: 50px; + text-align: center; box-shadow: 2px 2px 3px #999; } diff --git a/src/components/FloatingButton.js b/src/components/FloatingButton.js index 649cf96..c6415f5 100644 --- a/src/components/FloatingButton.js +++ b/src/components/FloatingButton.js @@ -1,11 +1,19 @@ import React from 'react'; +import { Link } from 'react-router'; const FloatingButton = (props) => { return ( -
-

- -

+
+ {props.to + ? +

+ +

+ + :

+ +

+ }
); }; diff --git a/src/components/NewPost.js b/src/components/NewPost.js index 77f17fd..7f7bf45 100644 --- a/src/components/NewPost.js +++ b/src/components/NewPost.js @@ -22,7 +22,7 @@ class NewPost extends Component { this.drizzle = context.drizzle; this.state = { - postSubjectInput: this.props.subject, + postSubjectInput: this.props.subject ? this.props.subject : "", postContentInput: '', postSubjectInputEmptySubmit: false, postContentInputEmptySubmit: false, diff --git a/src/components/ProfileInformation.js b/src/components/ProfileInformation.js index 4204995..ee1e7e2 100644 --- a/src/components/ProfileInformation.js +++ b/src/components/ProfileInformation.js @@ -25,6 +25,11 @@ const ProfileInformation = (props) => {

Number of posts: {props.numberOfPosts}

+ {props.dateOfRegister && +

+ Member since: {props.dateOfRegister} +

+ } {props.self && }
); diff --git a/src/components/TopicList.js b/src/components/TopicList.js index c29ab3b..9eb55fb 100644 --- a/src/components/TopicList.js +++ b/src/components/TopicList.js @@ -32,16 +32,24 @@ class TopicList extends Component { } async fetchSubject(topicIndex) { - /*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(this.props.topicIDs[topicIndex]); - this.topicsSubjects[topicIndex] = som['subject']; - this.topicsSubjectsFetchStatus[topicIndex] = "fetched"; + 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', () => { + 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 (){ diff --git a/src/containers/BoardContainer.js b/src/containers/BoardContainer.js index 8950d3d..d329940 100644 --- a/src/containers/BoardContainer.js +++ b/src/containers/BoardContainer.js @@ -1,7 +1,6 @@ import { drizzleConnect } from 'drizzle-react'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; import TopicList from '../components/TopicList'; import FloatingButton from '../components/FloatingButton'; @@ -34,9 +33,7 @@ class Board extends Component { return (
{boardContents} - - - +
); } diff --git a/src/containers/ProfileContainer.js b/src/containers/ProfileContainer.js index 90d2df7..2e62442 100644 --- a/src/containers/ProfileContainer.js +++ b/src/containers/ProfileContainer.js @@ -6,9 +6,11 @@ import ProfileInformation from '../components/ProfileInformation'; import TopicList from '../components/TopicList'; import PostList from '../components/PostList'; import LoadingSpinner from '../components/LoadingSpinner'; +import epochTimeConverter from '../helpers/EpochTimeConverter'; const contract = "Forum"; const contractMethods = { + getDateOfRegister: "getUserDateOfRegister", getOrbitDB: "getOrbitDBId", getUserTopics: "getUserTopics", getUserPosts: "getUserPosts" @@ -31,8 +33,10 @@ class Profile extends Component { this.state = { viewSelected: "topics", username: this.match.username, // TODO actually get them from match - userAddress: this.match.address, // when router is fixed + userAddress: this.match.userAddress, // when router is fixed + dateOfRegister: null, orbitDBId: this.match.address === this.props.user.address ? this.props.orbitDB.id : "", + getDateOfRegisterTransactionState: null, getOrbitDBTransactionState: this.match.address === this.props.user.address ? "SUCCESS" : null, getTopicsTransactionState: null, getPostsTransactionState: null, @@ -53,6 +57,7 @@ class Profile extends Component { orbitAddress={this.state.orbitDBId} numberOfTopics={this.state.topicIDs.length} numberOfPosts={this.state.postIDs.length} + dateOfRegister={this.state.dateOfRegister} self/>

(this.handleTabClick("topics"))} @@ -83,11 +88,29 @@ class Profile extends Component { } componentWillReceiveProps() { + 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 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); + .methods[contractMethods.getOrbitDB].cacheCall(this.state.userAddress); this.setState({'getOrbitDBTransactionState': "IN_PROGRESS"}); } } @@ -105,9 +128,8 @@ class Profile extends Component { 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); + .methods[contractMethods.getUserTopics].cacheCall(this.state.userAddress); this.setState({'getTopicsTransactionState': "IN_PROGRESS"}); } } @@ -125,9 +147,8 @@ class Profile extends Component { 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); + .methods[contractMethods.getUserPosts].cacheCall(this.state.userAddress); this.setState({'getPostsTransactionState': "IN_PROGRESS"}); } } diff --git a/src/containers/TopicContainer.js b/src/containers/TopicContainer.js index 1208c89..9bd1a46 100644 --- a/src/containers/TopicContainer.js +++ b/src/containers/TopicContainer.js @@ -63,18 +63,18 @@ class Topic extends Component { ); } else { topicContents = ( - this.state.posting - ?(

- {this.handleClick()}} - onPostCreated={() => {this.postCreated()}} - /> + (
+ {this.state.posting && + {this.handleClick()}} + onPostCreated={() => {this.postCreated()}} + /> + } -
) - :(
- - + {!this.state.posting && + + }
) ) } diff --git a/src/util/orbit.js b/src/util/orbit.js index 578ab60..55e9bdd 100644 --- a/src/util/orbit.js +++ b/src/util/orbit.js @@ -12,24 +12,25 @@ const ipfsOptions = { EXPERIMENTAL: { pubsub: true }, config: { - Addresses: { - Swarm: [] - } - }, -}; - -/*, - config: { Addresses: { Swarm: [ // Use IPFS dev signal server + // Prefer websocket over webrtc + // + // Websocket: + // '/dns4/ws-star-signal-2.servep2p.com/tcp/443//wss/p2p-websocket-star', + // '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star', + // Local signal server + //'/ip4/127.0.0.1/tcp/4711/ws/p2p-websocket-star' + // + // WebRTC: // '/dns4/star-signal.cloud.ipfs.team/wss/p2p-webrtc-star', - '/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star', - // Use local signal server - // '/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star', + // Local signal server + // '/ip4/127.0.0.1/tcp/1337/ws/p2p-webrtc-star' ] } - }*/ + }, +}; // Create IPFS instance const ipfs = new IPFS(ipfsOptions); @@ -39,7 +40,6 @@ ipfs.on('ready', async () => { store.dispatch({type: "IPFS_INITIALIZED"}); }); - async function createDatabases() { orbitdb = new OrbitDB(ipfs); topicsDB = await orbitdb.keyvalue('topics');