diff --git a/src/assets/css/App.css b/src/assets/css/App.css index d985067..da787e8 100644 --- a/src/assets/css/App.css +++ b/src/assets/css/App.css @@ -6,10 +6,6 @@ html, body { height: 100%; } -body { - font-family: 'Roboto', sans-serif; -} - strong { font-weight: bold !important; } @@ -57,6 +53,11 @@ strong { right: 0; } +.sidebar-message { + margin: 0px 5px 12px 12px; + padding: 0px; +} + .view-container { width: 100%; height: 100%; diff --git a/src/assets/css/index.css b/src/assets/css/index.css index b4cc725..ea1e941 100644 --- a/src/assets/css/index.css +++ b/src/assets/css/index.css @@ -1,5 +1,4 @@ body { margin: 0; padding: 0; - font-family: sans-serif; } diff --git a/src/assets/css/progress-bar.css b/src/assets/css/progress-bar.css new file mode 100644 index 0000000..aa8386d --- /dev/null +++ b/src/assets/css/progress-bar.css @@ -0,0 +1,108 @@ +/* Progress Bar */ + +.progress-bar-container { + position: absolute; + top: 54px; + left: 0px; + width: 100%; +} + +.progress { + position: relative; + height: 4px; + display: block; + width: 100%; + background-color: #acece6; + border-radius: 2px; + background-clip: padding-box; + margin: 0.5rem 0 1rem 0; + overflow: hidden; +} + +.progress .indeterminate { + background-color: #00b5ad; +} + +.progress .indeterminate:before { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + -webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; + animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; +} + +.progress .indeterminate:after { + content: ''; + position: absolute; + background-color: inherit; + top: 0; + left: 0; + bottom: 0; + will-change: left, right; + -webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite; + -webkit-animation-delay: 1.15s; + animation-delay: 1.15s; +} + +@-webkit-keyframes indeterminate { + 0% { + left: -35%; + right: 100%; + } + 60% { + left: 100%; + right: -90%; + } + 100% { + left: 100%; + right: -90%; + } +} + +@keyframes indeterminate { + 0% { + left: -35%; + right: 100%; + } + 60% { + left: 100%; + right: -90%; + } + 100% { + left: 100%; + right: -90%; + } +} +@-webkit-keyframes indeterminate-short { + 0% { + left: -200%; + right: 100%; + } + 60% { + left: 107%; + right: -8%; + } + 100% { + left: 107%; + right: -8%; + } +} +@keyframes indeterminate-short { + 0% { + left: -200%; + right: 100%; + } + 60% { + left: 107%; + right: -8%; + } + 100% { + left: 107%; + right: -8%; + } +} \ No newline at end of file diff --git a/src/assets/css/start-topic-container.css b/src/assets/css/start-topic-container.css index 44370a4..dd11687 100644 --- a/src/assets/css/start-topic-container.css +++ b/src/assets/css/start-topic-container.css @@ -3,8 +3,4 @@ .topic-form { width: 100%; margin: 20px 0px; -} - -.markdown-preview { - padding: 0px 20px; } \ No newline at end of file diff --git a/src/assets/css/topic-container.css b/src/assets/css/topic-container.css index 7a8bad1..0467228 100644 --- a/src/assets/css/topic-container.css +++ b/src/assets/css/topic-container.css @@ -1,5 +1,10 @@ /* POSTS LIST SCREEN */ +.posts-list-spacer { + margin-bottom: 85px; + height: 0px; +} + .post { width: 100%; background-color: #FFFFFF; diff --git a/src/components/NewPost.js b/src/components/NewPost.js index 380ebc8..5c240f2 100644 --- a/src/components/NewPost.js +++ b/src/components/NewPost.js @@ -1,7 +1,5 @@ -import { drizzleConnect } from 'drizzle-react'; import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import uuidv4 from 'uuid/v4'; +import { drizzleConnect } from 'drizzle-react'; import { Grid, Form, TextArea, Button, Icon, Divider } from 'semantic-ui-react' @@ -9,8 +7,7 @@ import TimeAgo from 'react-timeago'; import UserAvatar from 'react-user-avatar'; import ReactMarkdown from 'react-markdown'; -const contract = "Forum"; -const contractMethod = "createPost"; +import { createPost } from '../redux/actions/transactionsMonitorActions'; class NewPost extends Component { constructor(props, context) { @@ -23,20 +20,13 @@ class NewPost extends Component { this.newPostOuterRef = React.createRef(); - this.transactionProgressText = []; - this.drizzle = context.drizzle; - this.state = { postSubjectInput: this.props.subject ? this.props.subject : "", postContentInput: '', postSubjectInputEmptySubmit: false, postContentInputEmptySubmit: false, previewEnabled: false, - previewDate: "", - creatingPost: false, - transactionState: null, - savingToOrbitDB: null, - transactionOutputTimerActive: false + previewDate: "" }; } @@ -49,13 +39,14 @@ class NewPost extends Component { return; } - this.stackId = this.drizzle.contracts[contract].methods[contractMethod].cacheSend(this.props.topicID); - this.transactionProgressText.push(
); - this.transactionProgressText.push("Waiting for transaction acceptance..."); - this.setState({ - 'creatingPost': true, - 'transactionState': "ACCEPTANCE_PENDING" - }); + this.props.store.dispatch( + createPost(this.props.topicID, ((returnData) => { + this.topicIDFetched = returnData.topicID; + this.postIDFetched = returnData.postID; + this.pushToDatabase(); + })) + ); + this.props.onPostCreated(); } async pushToDatabase() { @@ -63,7 +54,6 @@ class NewPost extends Component { subject: this.state.postSubjectInput, content: this.state.postContentInput }); - this.setState({'savingToOrbitDB': "SUCCESS"}); } handleInputChange(event) { @@ -90,14 +80,6 @@ class NewPost extends Component { render() { return (
- {this.state.creatingPost &&
-
-

-
- {this.transactionProgressText} -
-
- } #{this.props.postIndex} @@ -184,156 +166,6 @@ class NewPost extends Component { ); } - 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(
); - 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(
); - this.transactionProgressText.push( - - Transaction completed successfully. - - ); - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - TopicID = {this.topicIDFetched}, PostID = {this.postIDFetched} - - ); - this.setState({'transactionState': "SUCCESS"}); - } else if (this.props.transactions[this.txHash].status === "error"){ - /* Transaction failed to complete */ - - //Updates output and state - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - Transaction failed to complete with error: - - ); - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - {this.props.transactions[this.txHash].error} - - ); - 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(
); - this.transactionProgressText.push( - - Post successfully saved in OrbitDB. - - ); - 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(
); - this.transactionProgressText.push( - - An error occurred while trying to save post in OrbitDB. - - ); - 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(
); - this.transactionProgressText.push( - - Transaction canceled. - - ); - 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); - } - } - } - componentDidUpdate(prevProps, prevState){ if (!this.state.previewEnabled && prevState.previewEnabled){ this.newPostOuterRef.current.scrollIntoView(true); @@ -345,14 +177,8 @@ class NewPost extends Component { } } -NewPost.contextTypes = { - drizzle: PropTypes.object -}; - const mapStateToProps = state => { return { - transactions: state.transactions, - transactionStack: state.transactionStack, orbitDB: state.orbitDB, user: state.user } diff --git a/src/components/PostList.js b/src/components/PostList.js index 8ca570e..86f8fc6 100644 --- a/src/components/PostList.js +++ b/src/components/PostList.js @@ -23,7 +23,7 @@ const PostList = (props) => { }); return ( -
+
{props.recentToTheTop ?posts.slice(0).reverse() :posts diff --git a/src/components/ProfileInformation.js b/src/components/ProfileInformation.js index 6d96eee..5c7ca37 100644 --- a/src/components/ProfileInformation.js +++ b/src/components/ProfileInformation.js @@ -17,46 +17,47 @@ const ProfileInformation = (props) => { transaction = props.blockchainData .find(transaction => transaction.callInfo.method === "getOrbitDBId") let orbitDBId = transaction ? transaction.returnData : ""; - return ( -
- {props.avatarUrl && } - - - - - - - - - - - - - - - - - - + + return ( +
+ {props.avatarUrl && } +
Username:{username}
Account address:{props.address}
OrbitDB:{orbitDBId}
Number of topics created:{props.numberOfTopics}
+ + + + + + + + + + + + + + + + + + + + + + {dateOfRegister && - - + + - {props.dateOfRegister && - - - - - } - -
Username:{username}
Account address:{props.address}
OrbitDB:{orbitDBId}
Number of topics created:{props.numberOfTopics}
Number of posts:{props.numberOfPosts}
Number of posts:{props.numberOfPosts}Member since:{epochTimeConverter(dateOfRegister)}
Member since:{epochTimeConverter(dateOfRegister)}
- {props.self && } -
- ); + } + + + {props.self && } +
+ ); }; export default ProfileInformation; \ No newline at end of file diff --git a/src/components/WithBlockchainData.js b/src/components/WithBlockchainData.js index 640e5da..414b118 100644 --- a/src/components/WithBlockchainData.js +++ b/src/components/WithBlockchainData.js @@ -12,11 +12,9 @@ class WithBlockchainData extends Component { this.forwardedProps = rest; } - this.checkContractUpdates = this.checkContractUpdates.bind(this); - this.drizzle = context.drizzle; this.dataKeys = []; - this.blockchainData = this.callsInfo.map((call) => { + let blockchainData = this.callsInfo.map((call) => { return ({ callInfo: call, status: "initialized", @@ -24,52 +22,53 @@ class WithBlockchainData extends Component { }); }); + //Initial call 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"; + blockchainData[i].status = "pending"; } this.state = { - transactionsState: new Array(this.callsInfo.length).fill("pending") + callState: new Array(this.callsInfo.length).fill("pending"), + blockchainData: blockchainData } } render() { - let {component, callsInfo, ...rest } = this.props; + let {component, callsInfo, ...rest } = this.props; //Update rest arguments return ( - + ); } - componentDidMount() { - this.intervalChecker = setInterval(this.checkContractUpdates, 10); //HOWMUCHMUCHACHO??? - } - - componentWillUnmount() { - clearInterval(this.intervalChecker); - } - - checkContractUpdates() { + componentDidUpdate(){ 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!! + let dataFetched = (currentDrizzleState + .contracts[this.callsInfo[i].contract][this.callsInfo[i].method][this.dataKeys[i]]); + if (dataFetched && dataFetched.value !== this.state.blockchainData[i].returnData){ + /* There are new data in the blockchain*/ + + //Immutable update + let newBlockchainData = this.state.blockchainData.map((callData, index) => { + if (index !== i) return callData; + return { + ...callData, + returnData: dataFetched.value, + status: "success" + } + }) + + let newStates = this.state.callState.slice(); + newStates[i] = "success" + this.setState({ + callState: newStates, + blockchainData: newBlockchainData + }); + } } } } diff --git a/src/containers/BoardContainer.js b/src/containers/BoardContainer.js index ba040f5..94c2597 100644 --- a/src/containers/BoardContainer.js +++ b/src/containers/BoardContainer.js @@ -7,13 +7,20 @@ import { Header } from 'semantic-ui-react'; import WithBlockchainData from '../components/WithBlockchainData'; import TopicList from '../components/TopicList'; import FloatingButton from '../components/FloatingButton'; -import LoadingSpinner from '../components/LoadingSpinner'; + +import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions'; class Board extends Component { constructor(props) { super(props); + this.props.store.dispatch(showProgressBar()); + this.handleCreateTopicClick = this.handleCreateTopicClick.bind(this); + + this.state = { + pageLoaded: false + } } handleCreateTopicClick() { @@ -22,17 +29,14 @@ class Board extends Component { render() { var boardContents; - if (!this.props.blockchainData[0].returnData) { - boardContents = ( - - ); - } else if (this.props.blockchainData[0].returnData !== '0'){ + if (this.props.blockchainData[0].returnData !== '0'){ this.topicIDs = []; for (var i = 0; i < this.props.blockchainData[0].returnData; i++) { this.topicIDs.push(i); } boardContents = ([ , +
, this.props.user.hasSignedUp && @@ -45,7 +49,7 @@ class Board extends Component { There are no topics yet!
- Sign up to be the first post. + Sign up to be the first to post.
); @@ -68,10 +72,16 @@ class Board extends Component { return (
{boardContents} -
); } + + componentDidUpdate(){ + if (!this.state.pageLoaded && this.props.blockchainData[0].returnData){ + this.props.store.dispatch(hideProgressBar()); + this.setState({ pageLoaded: true }); + } + } } Board.contextTypes = { diff --git a/src/containers/SignUpContainer.js b/src/containers/SignUpContainer.js index c4ec30f..eb68d63 100644 --- a/src/containers/SignUpContainer.js +++ b/src/containers/SignUpContainer.js @@ -5,6 +5,16 @@ import UsernameFormContainer from './UsernameFormContainer'; import { Header } from 'semantic-ui-react'; class SignUp extends Component { + constructor(props){ + super(props); + + this.signedUp = this.signedUp.bind(this); + } + + signedUp(){ + this.props.router.push("/home"); + } + render() { return ( this.props.user.hasSignedUp @@ -22,14 +32,13 @@ class SignUp extends Component {

Account address: {this.props.user.address}

- +
) ); } } -// May still need this even with data function to refresh component on updates for this contract. const mapStateToProps = state => { return { user: state.user diff --git a/src/containers/StartTopicContainer.js b/src/containers/StartTopicContainer.js index 3e67f49..5a85499 100644 --- a/src/containers/StartTopicContainer.js +++ b/src/containers/StartTopicContainer.js @@ -1,14 +1,11 @@ -import { drizzleConnect } from 'drizzle-react'; import React, { Component } from 'react'; +import { drizzleConnect } from 'drizzle-react'; import PropTypes from 'prop-types'; -import uuidv4 from 'uuid/v4'; import { Form, TextArea, Button, Icon } from 'semantic-ui-react' - import NewTopicPreview from '../components/NewTopicPreview' -const contract = "Forum"; -const contractMethod = "createTopic"; +import { createTopic } from '../redux/actions/transactionsMonitorActions'; class StartTopic extends Component { constructor(props, context) { @@ -19,9 +16,6 @@ class StartTopic extends Component { this.validateAndPost = this.validateAndPost.bind(this); this.pushToDatabase = this.pushToDatabase.bind(this); - this.transactionProgressText = []; - this.drizzle = context.drizzle; - this.state = { topicSubjectInput: '', topicMessageInput: '', @@ -29,10 +23,7 @@ class StartTopic extends Component { topicMessageInputEmptySubmit: false, previewEnabled: false, previewDate: "", - creatingTopic: false, - transactionState: null, - savingToOrbitDB: null, - transactionOutputTimerActive: false + creatingTopic: false }; } @@ -42,16 +33,19 @@ class StartTopic extends Component { topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', topicMessageInputEmptySubmit: this.state.topicMessageInput === '' }); - return; } - this.stackId = this.drizzle.contracts[contract].methods[contractMethod].cacheSend(); - this.transactionProgressText.push(
); - this.transactionProgressText.push("Waiting for transaction acceptance..."); + this.props.store.dispatch( + createTopic(((returnData) => { + this.topicIDFetched = returnData.topicID; + this.postIDFetched = returnData.postID; + this.pushToDatabase(); + this.props.router.push("/topic/" + this.topicIDFetched); + })) + ); this.setState({ - 'creatingTopic': true, - 'transactionState': "ACCEPTANCE_PENDING" + 'creatingTopic': true }); } @@ -64,7 +58,6 @@ class StartTopic extends Component { subject: this.state.topicSubjectInput, content: this.state.topicMessageInput }); - this.setState({'savingToOrbitDB': "SUCCESS"}); } handleInputChange(event) { @@ -97,13 +90,13 @@ class StartTopic extends Component { var previewEditText = this.state.previewEnabled ? "Edit" : "Preview"; return (
- {this.state.creatingTopic &&
+ {/*this.state.creatingTopic &&


{this.transactionProgressText}
-
+
*/ } {this.state.previewEnabled && ); } - - componentWillReceiveProps(){ - if(this.state.creatingTopic && !this.state.transactionOutputTimerActive){ - /* User submitted a new Topic */ - - 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(
); - 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 topic's id returned by contract - let topicData = this.props.transactions[this.txHash].receipt.events.TopicCreated - .returnValues; - this.topicIDFetched = topicData.topicID; - this.postIDFetched = topicData.postID; - - //Updates output and state - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - Transaction completed successfully. - - ); - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - TopicID = {this.topicIDFetched}, PostID = {this.postIDFetched} - - ); - this.setState({'transactionState': "SUCCESS"}); - } else if (this.props.transactions[this.txHash].status === "error"){ - /* Transaction failed to complete */ - - //Updates output and state - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - Transaction failed to complete with error: - - ); - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - {this.props.transactions[this.txHash].error} - - ); - this.setState({ - 'transactionState': "ERROR", - 'transactionOutputTimerActive': true - }); - this.transactionOutputTimer = setTimeout(() => { - this.transactionProgressText = []; - this.setState({ - 'creatingTopic': false, - 'transactionState': null, - 'savingToOrbitDB': null, - 'transactionOutputTimerActive': false - }); - }, 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(
); - this.transactionProgressText.push( - - Topic successfully saved in OrbitDB. - - ); - this.setState({'transactionOutputTimerActive': true}); - this.transactionOutputTimer = setTimeout(() => { - this.transactionProgressText = []; - this.setState({ - 'creatingTopic': false, - 'transactionState': null, - 'savingToOrbitDB': null, - 'transactionOutputTimerActive': false - }); - this.props.router.push("/topic/" + this.topicIDFetched); - }, 5000); - } - else if (this.state.savingToOrbitDB === "ERROR"){ - /* Failed to save data in OrbitDB */ - - //Updates output and state - this.transactionProgressText.push(
); - this.transactionProgressText.push( - - An error occurred while trying to save post in OrbitDB. - - ); - this.setState({'transactionOutputTimerActive': true}); - this.transactionOutputTimer = setTimeout(() => { - this.transactionProgressText = []; - this.setState({ - 'creatingTopic': false, - 'transactionState': null, - 'savingToOrbitDB': null, - 'transactionOutputTimerActive': false - }); - }, 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(
); - this.transactionProgressText.push( - - Transaction canceled. - - ); - this.setState({'transactionState': "SUCCESS"}); - this.setState({'transactionOutputTimerActive': true}); - this.transactionOutputTimer = setTimeout(() => { - this.transactionProgressText = []; - this.setState({ - 'creatingTopic': false, - 'transactionState': null, - 'savingToOrbitDB': null, - 'transactionOutputTimerActive': false - }); - }, 5000); - } - } - } } StartTopic.contextTypes = { - drizzle: PropTypes.object, router: PropTypes.object }; diff --git a/src/containers/TopicContainer.js b/src/containers/TopicContainer.js index 88001ee..1b3ccdf 100644 --- a/src/containers/TopicContainer.js +++ b/src/containers/TopicContainer.js @@ -1,47 +1,44 @@ -import { drizzleConnect } from 'drizzle-react'; import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import { drizzleConnect } from 'drizzle-react'; import WithBlockchainData from '../components/WithBlockchainData'; import PostList from '../components/PostList'; import NewPost from '../components/NewPost'; import FloatingButton from '../components/FloatingButton'; -import LoadingSpinner from '../components/LoadingSpinner'; + +import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions'; class Topic extends Component { - constructor(props, context) { + constructor(props) { super(props); + this.props.store.dispatch(showProgressBar()); + this.fetchTopicSubject = this.fetchTopicSubject.bind(this); - this.handleClick = this.handleClick.bind(this); + this.togglePostingState = this.togglePostingState.bind(this); this.postCreated = this.postCreated.bind(this); - this.drizzle = context.drizzle; - this.state = { topicID: this.props.params.topicId, - topicSubject: this.props.params.topicSubject ? this.props.params.topicSubject : null, + topicSubject: null, postFocus: this.props.params.postId && /^[0-9]+$/.test(this.props.params.postId) ? this.props.params.postId : null, - getPostsTransactionState: null, + fetchTopicSubjectStatus: null, posting: false }; } async fetchTopicSubject(orbitDBAddress) { - /*const fullAddress = this.topicsData[this.state.topicID][1]; - const store = await this.props.orbitDB.orbitdb.keyvalue(JSON.stringify(fullAddress)); - await store.load(); - var som = store.get(JSON.stringify(this.state.topicID)); - this.topicsSubjects[this.state.topicID] = som['subject']; - this.topicsSubjectsFetchStatus[this.state.topicID] = "fetched";*/ - - var som =this.props.orbitDB.topicsDB.get(this.state.topicID); - this.setState({'topicSubject': som['subject']}); + var orbitData =this.props.orbitDB.topicsDB.get(this.state.topicID); + this.props.store.dispatch(hideProgressBar()); + this.setState({ + 'topicSubject': orbitData['subject'], + fetchTopicSubjectStatus: "fetched" + }); } - handleClick(event) { + togglePostingState(event) { if (event){ event.preventDefault(); } @@ -52,31 +49,28 @@ class Topic extends Component { postCreated(){ this.setState(prevState => ({ - getPostsTransactionState: null, posting: false })); + //TODO reload topic } render() { var topicContents; - if (this.state.getPostsTransactionState !== "SUCCESS") { - topicContents = ( - - ); - } else { + if (this.props.blockchainData[0].status === "success") { topicContents = ( - (
- {this.postList} + (
+ {this.state.posting && {this.handleClick()}} + onCancelClick={() => {this.togglePostingState()}} onPostCreated={() => {this.postCreated()}} /> } +
{this.props.user.hasSignedUp && !this.state.posting && - + }
) ) @@ -92,32 +86,16 @@ class Topic extends Component { ); } - componentWillReceiveProps() { + componentDidUpdate() { if (this.props.blockchainData[0].status === "success") { - if (this.state.getPostsTransactionState !== "SUCCESS"){ - this.postList = { - return { - contract: 'Forum', - method: 'getPost', - params: [postID] - } - })} - postIDs={this.props.blockchainData[0].returnData[4]} - /> - - this.setState({'getPostsTransactionState': "SUCCESS"}); + if (this.state.fetchTopicSubjectStatus === null){ + this.setState({ fetchTopicSubjectStatus: "fetching"}) this.fetchTopicSubject(this.props.blockchainData[0].returnData[0]); } } } } -Topic.contextTypes = { - drizzle: PropTypes.object -}; - const mapStateToProps = state => { return { user: state.user, @@ -132,20 +110,20 @@ class TopicContainer extends Component { if (!/^[0-9]+$/.test(props.params.topicId)){ //Topic ID should be a positive integer this.props.router.push("/404"); } - - this.topic = ; - } - - render() { - return(this.topic); + params={this.props.params} + /> + ); } } diff --git a/src/containers/TransactionsMonitorContainer.js b/src/containers/TransactionsMonitorContainer.js new file mode 100644 index 0000000..4e0ca91 --- /dev/null +++ b/src/containers/TransactionsMonitorContainer.js @@ -0,0 +1,172 @@ +import React, { Component } from 'react'; +import { drizzleConnect } from 'drizzle-react'; +import PropTypes from 'prop-types'; + +import { Message } from 'semantic-ui-react'; + +import { updateTransaction } from '../redux/actions/transactionsMonitorActions'; + +class RightSideBar extends Component { + constructor(props, context) { + super(props); + + this.handleMessageDismiss = this.handleMessageDismiss.bind(this); + + this.drizzle = context.drizzle; + this.transactionsStackIds = []; + this.transactionsTxHashes = []; + + this.state = { + transactionsCompletionTime: [], + isTransactionMessageActive: [] + } + } + + handleMessageDismiss(messageIndex) { + let isTransactionMessageActiveShallowCopy = this.state.isTransactionMessageActive.slice(); + isTransactionMessageActiveShallowCopy[messageIndex] = false; + this.setState({ + isTransactionMessageActive: isTransactionMessageActiveShallowCopy + }); + } + + render() { + let transactionMessages = this.props.transactionsQueue.map((transaction, index) => { + if (!this.state.isTransactionMessageActive[index]){ + return null; + } + let color = 'black'; + let message = []; + + while(true) { + if (transaction.status === 'initialized') break; + message.push("New transaction has been queued and is waiting your confirmation."); + + if (transaction.status === 'acceptance_pending') break; + message.push(
); + message.push("- transaction confirmed"); + + if (transaction.status === 'mining_pending') break; + message.push(
); + message.push("- transaction mined"); + + if (transaction.status === 'success') { + color = 'green'; + message.push(
); + message.push("- transaction completed successfully"); + break; + } + if (transaction.status === 'error') { + color = 'red'; + message.push(
); + message.push("Transaction failed to complete!"); + break; + } + } + + return ( +
+ {this.handleMessageDismiss(index)}}> + {message} + +
+ ); + }); + + return (transactionMessages); + } + + componentDidUpdate(){ + for (var index = 0; index < this.props.transactionsQueue.length; ++index) { + let transaction = this.props.transactionsQueue[index]; + + if (transaction.status === 'initialized' && + this.transactionsStackIds[index] === undefined){ + /* User submitted a new transaction */ + + let isTransactionMessageActiveShallowCopy = this.state + .isTransactionMessageActive.slice(); + isTransactionMessageActiveShallowCopy[index] = true; + this.setState({ + isTransactionMessageActive: isTransactionMessageActiveShallowCopy + }); + + this.transactionsStackIds[index] = (this.drizzle + .contracts[transaction.contract] + .methods[transaction.method] + .cacheSend(...(transaction.params))); + this.props.store.dispatch(updateTransaction(index, { + status: 'acceptance_pending' + })); + } else if (transaction.status === 'acceptance_pending'){ + if (this.props.transactionStack[this.transactionsStackIds[index]]){ + /* User confirmed the transaction */ + + //Gets transaciton's hash + this.transactionsTxHashes[index] = (this.props + .transactionStack[this.transactionsStackIds[index]]); + this.props.store.dispatch(updateTransaction(index, { + status: 'mining_pending' + })); + } + } else if (transaction.status === 'mining_pending'){ + if (this.props.transactions[this.transactionsTxHashes[index]] + .status === "success"){ + /* Transaction completed successfully */ + + //Gets returned data by contract + let data = this.props.transactions[this.transactionsTxHashes[index]] + .receipt.events[transaction.event].returnValues; + + this.props.store.dispatch(updateTransaction(index, { + status: 'success', + returnData: data + })); + + let transactionsCompletionTimeShallowCopy = this.state + .transactionsCompletionTime.slice(); + transactionsCompletionTimeShallowCopy[index] = new Date().getTime(); + this.setState({ + transactionsCompletionTime: transactionsCompletionTimeShallowCopy + }); + if (this.props.transactionsQueue[index].callback){ + this.props.transactionsQueue[index].callback(data); + } + } else if (this.props.transactions[this.transactionsTxHashes[index]] + .status === "error"){ + /* Transaction failed to complete */ + + this.props.store.dispatch(updateTransaction(index, { + status: 'error' + })); + + let transactionsCompletionTimeShallowCopy = this.state + .transactionsCompletionTime.slice(); + transactionsCompletionTimeShallowCopy[index] = new Date().getTime(); + this.setState({ + transactionsCompletionTime: transactionsCompletionTimeShallowCopy + }); + if (this.props.transactionsQueue[index].callback){ + this.props.transactionsQueue[index].callback(null); + } + } + } + } + } +} + +RightSideBar.contextTypes = { + drizzle: PropTypes.object +}; + +const mapStateToProps = state => { + return { + transactionsQueue: state.transactionsQueue.transactions, + transactions: state.transactions, + transactionStack: state.transactionStack + } +}; + +const RightSideBarContainer = drizzleConnect(RightSideBar, mapStateToProps); + +export default RightSideBarContainer; \ No newline at end of file diff --git a/src/containers/UsernameFormContainer.js b/src/containers/UsernameFormContainer.js index d38eee8..d6a36ce 100644 --- a/src/containers/UsernameFormContainer.js +++ b/src/containers/UsernameFormContainer.js @@ -3,11 +3,12 @@ import { drizzleConnect } from 'drizzle-react'; import PropTypes from 'prop-types'; import { Button, Message, Form, Dimmer, Loader, Header } from 'semantic-ui-react'; + import { createDatabases } from './../util/orbit'; +import { updateUsername } from '../redux/actions/transactionsMonitorActions'; const contract = "Forum"; const signUpMethod = "signUp"; -const updateUsernameMethod ="updateUsername"; class UsernameFormContainer extends Component { constructor(props, context) { @@ -22,7 +23,7 @@ class UsernameFormContainer extends Component { this.state = { usernameInput: '', error: false, - completingAction: false + signingUp: false }; } @@ -39,14 +40,26 @@ class UsernameFormContainer extends Component { } async completeAction() { - this.setState({ completingAction: true }); - if(this.props.user.hasSignedUp) - this.contracts[contract].methods[updateUsernameMethod].cacheSend(...[this.state.usernameInput]); - else - { + if(this.props.user.hasSignedUp){ + this.props.store.dispatch(updateUsername(...[this.state.usernameInput], null)); + } else { + this.setState({ signingUp: true }); const orbitdbInfo = await createDatabases(); - this.contracts[contract].methods[signUpMethod].cacheSend(...[this.state.usernameInput, orbitdbInfo.id, - orbitdbInfo.topicsDB, orbitdbInfo.postsDB, orbitdbInfo.publicKey, orbitdbInfo.privateKey]); + this.contracts[contract].methods[signUpMethod] + .cacheSend(...[this.state.usernameInput, + orbitdbInfo.id, + orbitdbInfo.topicsDB, + orbitdbInfo.postsDB, + orbitdbInfo.publicKey, + orbitdbInfo.privateKey + ]); + } + this.setState({ usernameInput: '' }); + } + + componentWillReceiveProps(nextProps){ + if (this.state.signingUp && nextProps.user.hasSignedUp){ + this.props.signedUp(); } } @@ -77,7 +90,7 @@ class UsernameFormContainer extends Component { /> - +
Magic elfs are processing your nobel request.
diff --git a/src/layouts/CoreLayout/CoreLayout.js b/src/layouts/CoreLayout/CoreLayout.js index 41127e2..6739f43 100644 --- a/src/layouts/CoreLayout/CoreLayout.js +++ b/src/layouts/CoreLayout/CoreLayout.js @@ -1,5 +1,8 @@ import React, { Component } from 'react'; +import { drizzleConnect } from 'drizzle-react'; + import NavBar from '../../components/NavBar'; +import TransactionsMonitorContainer from '../../containers/TransactionsMonitorContainer'; // Styles import '../../assets/fonts/fontawesome-free-5.0.13/fontawesome-all.js'; @@ -11,12 +14,19 @@ import '../../assets/css/board-container.css'; import '../../assets/css/start-topic-container.css'; import '../../assets/css/topic-container.css'; import '../../assets/css/profile-container.css'; +import '../../assets/css/progress-bar.css'; class CoreLayout extends Component { render() { return (
+
+
+
+
+
@@ -26,6 +36,7 @@ class CoreLayout extends Component {
@@ -33,4 +44,10 @@ class CoreLayout extends Component { } } -export default CoreLayout; \ No newline at end of file +const mapStateToProps = state => { + return { + isProgressBarVisible: state.interface.displayProgressBar + } +}; + +export default drizzleConnect(CoreLayout, mapStateToProps) \ No newline at end of file diff --git a/src/redux/actions/transactionsMonitorActions.js b/src/redux/actions/transactionsMonitorActions.js new file mode 100644 index 0000000..7492a4f --- /dev/null +++ b/src/redux/actions/transactionsMonitorActions.js @@ -0,0 +1,54 @@ +//Action creators + +export const INIT_TRANSACTION = 'INIT_TRANSACTION'; +export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION'; + +export function updateUsername(newUsername, callback){ + return { + type: INIT_TRANSACTION, + transactionDescriptor: + { + contract: 'Forum', + method: 'updateUsername', + params: [newUsername], + event: 'UsernameUpdated', + }, + callback: callback + }; +} + +export function createTopic(callback){ + return { + type: INIT_TRANSACTION, + transactionDescriptor: + { + contract: 'Forum', + method: 'createTopic', + params: [], + event: 'TopicCreated', + }, + callback: callback + }; +} + +export function createPost(topicID, callback){ + return { + type: INIT_TRANSACTION, + transactionDescriptor: + { + contract: 'Forum', + method: 'createPost', + params: [topicID], + event: 'PostCreated', + }, + callback: callback + }; +} + +export function updateTransaction(transactionIndex, updateDescriptor){ + return { + type: UPDATE_TRANSACTION, + index: transactionIndex, + transactionUpdates: updateDescriptor + }; +} \ No newline at end of file diff --git a/src/redux/actions/userInterfaceActions.js b/src/redux/actions/userInterfaceActions.js new file mode 100644 index 0000000..77591e0 --- /dev/null +++ b/src/redux/actions/userInterfaceActions.js @@ -0,0 +1,12 @@ +//Action creators + +export const SHOW_PROGRESS_BAR = 'SHOW_PROGRESS_BAR'; +export const HIDE_PROGRESS_BAR = 'HIDE_PROGRESS_BAR'; + +export function showProgressBar(){ + return { type: 'SHOW_PROGRESS_BAR'}; +} + +export function hideProgressBar(){ + return { type: 'HIDE_PROGRESS_BAR'}; +} \ No newline at end of file diff --git a/src/redux/reducer/reducer.js b/src/redux/reducer/reducer.js index a18bf91..aacc6ea 100644 --- a/src/redux/reducer/reducer.js +++ b/src/redux/reducer/reducer.js @@ -4,12 +4,17 @@ import { drizzleReducers } from 'drizzle'; import userReducer from "./userReducer"; import contractReducer from "./contractReducer"; import orbitReducer from "../../util/orbitReducer"; +import userInterfaceReducer from "./userInterfaceReducer"; +import transactionsMonitorReducer from "./transactionsMonitorReducer"; + const reducer = combineReducers({ routing: routerReducer, user: userReducer, orbitDB: orbitReducer, forumContract: contractReducer, + interface: userInterfaceReducer, + transactionsQueue: transactionsMonitorReducer, ...drizzleReducers }); diff --git a/src/redux/reducer/transactionsMonitorReducer.js b/src/redux/reducer/transactionsMonitorReducer.js new file mode 100644 index 0000000..c3111cf --- /dev/null +++ b/src/redux/reducer/transactionsMonitorReducer.js @@ -0,0 +1,39 @@ +import { INIT_TRANSACTION, UPDATE_TRANSACTION } from '../actions/transactionsMonitorActions'; + +const initialState = { + transactions: [] +}; + +const transactionsReducer = (state = initialState, action) => { + switch (action.type) { + case INIT_TRANSACTION: + let transactionsShallowCopy = state.transactions.slice(); + transactionsShallowCopy.push({ + status: 'initialized', + contract: action.transactionDescriptor.contract, + method: action.transactionDescriptor.method, + params: action.transactionDescriptor.params, + event: action.transactionDescriptor.event, + returnData: null, + callback: action.callback + }); + return { + transactions: transactionsShallowCopy + }; + case UPDATE_TRANSACTION: + return { transactions: state.transactions.map( (transaction, index) => { + if (index !== action.index){ + return transaction; + } + + return { + ...transaction, + ...action.transactionUpdates + } + })}; + default: + return state; + } +}; + +export default transactionsReducer; \ No newline at end of file diff --git a/src/redux/reducer/userInterfaceReducer.js b/src/redux/reducer/userInterfaceReducer.js new file mode 100644 index 0000000..e0baf4f --- /dev/null +++ b/src/redux/reducer/userInterfaceReducer.js @@ -0,0 +1,22 @@ +import { SHOW_PROGRESS_BAR, HIDE_PROGRESS_BAR } from '../actions/userInterfaceActions'; + +const initialState = { + displayProgressBar: false +}; + +const userInterfaceReducer = (state = initialState, action) => { + switch (action.type) { + case SHOW_PROGRESS_BAR: + return { + displayProgressBar: true + }; + case HIDE_PROGRESS_BAR: + return { + displayProgressBar: false + }; + default: + return state; + } +}; + +export default userInterfaceReducer; \ No newline at end of file