Browse Source

Improve contract interaction, Rebase, Minor style changes

develop
Apostolos Fanakis 7 years ago
parent
commit
6f861ebd6d
  1. 1
      package.json
  2. 2
      src/assets/css/App.css
  3. 200
      src/components/StartTopic.js
  4. 2
      src/components/TopicList.js
  5. 40
      src/containers/BoardContainer.js
  6. 297
      src/containers/StartTopicContainer.js
  7. 2
      src/index.js

1
package.json

@ -27,6 +27,7 @@
"react-user-avatar": "^1.10.0", "react-user-avatar": "^1.10.0",
"redux": "^3.7.2", "redux": "^3.7.2",
"redux-saga": "0.16.0", "redux-saga": "0.16.0",
"uuid": "^3.2.1",
"web3": "^1.0.0-beta.34" "web3": "^1.0.0-beta.34"
}, },
"scripts": { "scripts": {

2
src/assets/css/App.css

@ -170,7 +170,7 @@ body,
} }
.topic-form textarea { .topic-form textarea {
height: 200px; min-height: 200px;
} }
.form-input-required { .form-input-required {

200
src/components/StartTopic.js

@ -1,200 +0,0 @@
import { drizzleConnect } from 'drizzle-react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Post from './Post'
const contract = "Forum";
const contractMethod = "createTopic";
class StartTopic extends Component {
constructor(props, context) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handlePreviewToggle = this.handlePreviewToggle.bind(this);
this.validateAndPost = this.validateAndPost.bind(this);
this.pushToDatabase = this.pushToDatabase.bind(this);
this.transactionProgressText = [];
this.drizzle = context.drizzle;
this.state = {
topicSubjectInput: '',
topicMessageInput: '',
topicSubjectInputEmptySubmit: false,
topicMessageInputEmptySubmit: false,
previewEnabled: false,
previewDate: "",
creatingTopic: false,
transactionState: null,
savingToOrbitDB: null
};
}
async validateAndPost() {
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput === ''){
this.setState({
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '',
topicMessageInputEmptySubmit: this.state.topicMessageInput === ''
});
return;
}
this.stackId = this.drizzle.contracts[contract].methods[contractMethod].cacheSend();
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push("Waiting for transaction acceptance...");
this.setState({
'creatingTopic': true,
'transactionState': "ACCEPTANCE_PENDING"
});
}
async pushToDatabase() {
await this.props.orbitDB.topicsDB.put(this.topicIDFetched, {
subject: this.state.topicSubjectInput,
content: this.state.topicMessageInput
});
this.setState({'savingToOrbitDB': "SUCCESS"});
}
handleInputChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handlePreviewToggle() {
this.setState((prevState, props) => ({
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() {
return(
<div>
{this.state.creatingTopic && <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="pure-u-1-1 start-topic-back-button">
<p className="no-margin" onClick={this.props.onClick}>
<i className="fas fa-arrow-left fa-3x"></i>
</p>
</div>
{this.state.previewEnabled &&
<Post avatarUrl={this.props.user.avatarUrl}
username={this.props.user.username}
subject={this.state.topicSubjectInput}
date={this.state.previewDate}
postContent={this.state.topicMessageInput}
id={0}/>}
<form className="topic-form">
{!this.state.previewEnabled &&
[
<input key={"topicSubjectInput"}
name={"topicSubjectInput"}
className={this.state.topicSubjectInputEmptySubmit && "form-input-required"}
type="text"
value={this.state.topicSubjectInput}
placeholder="Subject"
id="topicSubjectInput"
onChange={this.handleInputChange} />,
<textarea key={"topicMessageInput"}
name={"topicMessageInput"}
className={this.state.topicMessageInputEmptySubmit && "form-input-required"}
value={this.state.topicMessageInput}
placeholder="Post"
id="topicMessageInput"
onChange={this.handleInputChange} />
]}
<button key="submit"
className="pure-button"
type="button"
onClick={this.validateAndPost}>
Post
</button>
<button className="pure-button margin-left-small"
type="button"
onClick={this.handlePreviewToggle}>
{this.state.previewEnabled ? "Edit" : "Preview"}
</button>
</form>
</div>
);
}
componentWillReceiveProps(){ //Maybe change it with this: https://redux.js.org/api-reference/store#subscribe
let currentDrizzleState = this.drizzle.store.getState();
if(this.state.creatingTopic){
if (this.state.transactionState === "ACCEPTANCE_PENDING" &&
currentDrizzleState.transactionStack[this.stackId]) {
this.txHash = currentDrizzleState.transactionStack[this.stackId];
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push("Transaction in progress: txHash = " + this.txHash);
this.setState({'transactionState': "IN_PROGRESS"});
} else if (this.state.transactionState === "IN_PROGRESS") {
if (currentDrizzleState.transactions[this.txHash].status === "success"){
this.topicIDFetched = currentDrizzleState.transactions[this.txHash].receipt
.events.TopicCreated.returnValues.topicID;
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push("Transaction completed successfully.");
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push("TopicID = " + this.topicIDFetched);
this.setState({'transactionState': "SUCCESS"});
} else if (currentDrizzleState.transactions[this.txHash].status === "error"){
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push("Transaction failed to complete.");
this.setState({'transactionState': "ERROR"});
}
} else if (this.state.transactionState === "SUCCESS") {
this.pushToDatabase();
if (this.state.savingToOrbitDB === "SUCCESS"){
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push("Post successfully saved in OrbitDB.");
this.setState({creatingTopic: false});
} else if (this.state.savingToOrbitDB === "ERROR"){
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push(<span style={{color: 'red'}}><strong>
An error occurred while trying to save post in OrbitDB.
</strong></span>);
this.setState({creatingTopic: false});
}
} else if (this.state.transactionState === "ERROR"){
this.transactionProgressText.push(<br/>);
this.transactionProgressText.push(<span style={{color: 'red'}}><strong>
An error occurred while trying to complete transaction.
</strong></span>);
this.setState({creatingTopic: false});
}
}
}
}
StartTopic.contextTypes = {
drizzle: PropTypes.object
};
const mapStateToProps = state => {
return {
orbitDB: state.orbitDB,
user: state.user
}
};
export default drizzleConnect(StartTopic, mapStateToProps);

2
src/components/TopicList.js

@ -54,7 +54,7 @@ class TopicList extends Component {
} }
render (){ render (){
const topics = this.topicsData.map((topic, index) => { const topics = this.topicsData.slice(0).reverse().map((topic, index) => {
if (topic){ if (topic){
return ( return (
<Link to={"/topic/" + index + "/" + <Link to={"/topic/" + index + "/" +

40
src/containers/BoardContainer.js

@ -1,10 +1,10 @@
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 PropTypes from 'prop-types';
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 StartTopic from '../components/StartTopic';
const contract = "Forum"; const contract = "Forum";
const contractMethod = "getNumberOfTopics"; const contractMethod = "getNumberOfTopics";
@ -13,27 +13,17 @@ class Board extends Component {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this.handleClick = this.handleClick.bind(this);
this.drizzle = context.drizzle; this.drizzle = context.drizzle;
this.dataKey = this.drizzle.contracts[contract].methods[contractMethod].cacheCall();
this.state = { this.state = {
startingNewTopic: false, startingNewTopic: false,
transactionState: "IN_PROGRESS" transactionState: null
}; };
} }
handleClick(event) {
event.preventDefault();
this.setState(prevState => ({
startingNewTopic: !prevState.startingNewTopic
}));
}
render() { render() {
var boardContents; var boardContents;
if (this.state.transactionState === "IN_PROGRESS") { if (this.state.transactionState !== "SUCCESS") {
boardContents = ( boardContents = (
<div className="center-in-parent"> <div className="center-in-parent">
<p> <p>
@ -46,20 +36,25 @@ class Board extends Component {
} }
return ( return (
this.state.startingNewTopic <div style={{marginBottom: '100px'}}>
?(<div>
<StartTopic onClick={this.handleClick}/>
</div>)
:(<div style={{marginBottom: '100px'}}>
{boardContents} {boardContents}
<Link to="/startTopic">
<FloatingButton onClick={this.handleClick}/> <FloatingButton onClick={this.handleClick}/>
</div>) </Link>
</div>
); );
} }
componentWillReceiveProps() { componentWillReceiveProps() {
if (this.state.transactionState === null){
if (this.drizzle.contracts[contract]){
//This gets called only once but should be called every time someone posts
this.dataKey = this.drizzle.contracts[contract].methods[contractMethod].cacheCall();
this.setState({'transactionState': "IN_PROGRESS"});
}
}
if (!this.numberOfTopics) { if (!this.numberOfTopics) {
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
@ -75,8 +70,7 @@ Board.contextTypes = {
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
user: state.user, user: state.user
orbitDB: state.orbitDB,
} }
}; };

297
src/containers/StartTopicContainer.js

@ -0,0 +1,297 @@
import { drizzleConnect } from 'drizzle-react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import uuidv4 from 'uuid/v4';
import Post from '../components/Post'
const contract = "Forum";
const contractMethod = "createTopic";
class StartTopic extends Component {
constructor(props, context) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handlePreviewToggle = this.handlePreviewToggle.bind(this);
this.validateAndPost = this.validateAndPost.bind(this);
this.pushToDatabase = this.pushToDatabase.bind(this);
this.transactionProgressText = [];
this.drizzle = context.drizzle;
this.state = {
topicSubjectInput: '',
topicMessageInput: '',
topicSubjectInputEmptySubmit: false,
topicMessageInputEmptySubmit: false,
previewEnabled: false,
previewDate: "",
creatingTopic: false,
transactionState: null,
savingToOrbitDB: null,
transactionOutputTimerActive: false
};
}
async validateAndPost() {
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput === ''){
this.setState({
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '',
topicMessageInputEmptySubmit: this.state.topicMessageInput === ''
});
return;
}
this.stackId = this.drizzle.contracts[contract].methods[contractMethod].cacheSend();
this.transactionProgressText.push(<br key={uuidv4()}/>);
this.transactionProgressText.push("Waiting for transaction acceptance...");
this.setState({
'creatingTopic': true,
'transactionState': "ACCEPTANCE_PENDING"
});
}
async pushToDatabase() {
await this.props.orbitDB.topicsDB.put(this.topicIDFetched, {
subject: this.state.topicSubjectInput,
content: this.state.topicMessageInput
});
this.setState({'savingToOrbitDB': "SUCCESS"});
}
handleInputChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handlePreviewToggle() {
this.setState((prevState, props) => ({
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() {
return(
<div>
{this.state.creatingTopic && <div id="overlay">
<div id="overlay-content">
<p><i className="fas fa-spinner fa-3x fa-spin"></i></p>
<br/>
{this.transactionProgressText}
</div>
</div>
}
{this.state.previewEnabled &&
<Post avatarUrl={this.props.user.avatarUrl}
username={this.props.user.username}
subject={this.state.topicSubjectInput}
date={this.state.previewDate}
postContent={this.state.topicMessageInput}
id={0}/>}
<form className="topic-form">
{!this.state.previewEnabled &&
[
<input key={"topicSubjectInput"}
name={"topicSubjectInput"}
className={this.state.topicSubjectInputEmptySubmit ? "form-input-required" : ""}
type="text"
value={this.state.topicSubjectInput}
placeholder="Subject"
id="topicSubjectInput"
onChange={this.handleInputChange} />,
<textarea key={"topicMessageInput"}
name={"topicMessageInput"}
className={this.state.topicMessageInputEmptySubmit ? "form-input-required" : ""}
value={this.state.topicMessageInput}
placeholder="Post"
id="topicMessageInput"
onChange={this.handleInputChange} />
]}
<button key="submit"
className="pure-button"
type="button"
onClick={this.validateAndPost}>
Post
</button>
<button className="pure-button margin-left-small"
type="button"
onClick={this.handlePreviewToggle}>
{this.state.previewEnabled ? "Edit" : "Preview"}
</button>
</form>
</div>
);
}
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(<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 topic's id returned by contract
this.topicIDFetched = this.props.transactions[this.txHash].receipt
.events.TopicCreated.returnValues.topicID;
//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}
</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({
'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(<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({
'creatingTopic': false,
'transactionState': null,
'savingToOrbitDB': null,
'transactionOutputTimerActive': false
});
}, 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({
'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(<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({
'creatingTopic': false,
'transactionState': null,
'savingToOrbitDB': null,
'transactionOutputTimerActive': false
});
}, 5000);
}
}
}
}
StartTopic.contextTypes = {
drizzle: PropTypes.object
};
const mapStateToProps = state => {
return {
transactions: state.transactions,
transactionStack: state.transactionStack,
orbitDB: state.orbitDB,
user: state.user
}
};
const StartTopicContainer = drizzleConnect(StartTopic, mapStateToProps)
export default StartTopicContainer;

2
src/index.js

@ -13,6 +13,7 @@ import PrivateRouteContainer from './containers/PrivateRouteContainer';
import HomeContainer from './containers/HomeContainer'; import HomeContainer from './containers/HomeContainer';
import TopicContainer from './containers/TopicContainer'; import TopicContainer from './containers/TopicContainer';
import StartTopicContainer from './containers/StartTopicContainer';
import ProfileContainer from './containers/ProfileContainer'; import ProfileContainer from './containers/ProfileContainer';
import NotFoundView from './components/NotFoundView'; import NotFoundView from './components/NotFoundView';
@ -32,6 +33,7 @@ render((
<IndexRoute component={HomeContainer} /> <IndexRoute component={HomeContainer} />
<PrivateRouteContainer path="/topic/:topicId/:topicSubject" component={TopicContainer} redirectTo="/" /> <PrivateRouteContainer path="/topic/:topicId/:topicSubject" component={TopicContainer} redirectTo="/" />
<PrivateRouteContainer path='/profile' component={ProfileContainer} redirectTo="/" /> <PrivateRouteContainer path='/profile' component={ProfileContainer} redirectTo="/" />
<PrivateRouteContainer path='/startTopic' component={StartTopicContainer} redirectTo="/" />
<Route path='/404' component={NotFoundView} /> <Route path='/404' component={NotFoundView} />
<Route path='*' component={NotFoundView} /> <Route path='*' component={NotFoundView} />
</Route> </Route>

Loading…
Cancel
Save