Skip to content

Commit

Permalink
added tx notifications to campaign deployer
Browse files Browse the repository at this point in the history
  • Loading branch information
ewingrj committed Jul 28, 2017
1 parent 698e6f1 commit fc07a92
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 4 deletions.
26 changes: 23 additions & 3 deletions dapp/js/actions/campaignDeployment.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions dapp/js/actions/transactions.js
@@ -0,0 +1,16 @@
import { transactionActions } from "../constants";

export const newTransaction = hash => ({
type: transactionActions.NEW_TRANSATION,
hash,
});

export const transactionMined = hash => ({
type: transactionActions.TRANSACTION_MINED,
hash,
});

export const dismissTransaction = hash => ({
type: transactionActions.DISMISS_TRANSACTION,
hash,
});
6 changes: 6 additions & 0 deletions dapp/js/blockchain/Web3.js
Expand Up @@ -18,30 +18,36 @@ web3.eth.getTransactionReceiptMined = (txHash, blockLimit = 15) => new Promise(
web3.eth.getTransactionReceipt(txHash, (err, res) => {
if (err) {
reject(err);
return;
}

// tx already mined
if (res) {
resolve(res);
return;
}

let blockCounter = blockLimit;
const listener = web3.eth.filter("latest").watch((blockErr) => {
if (blockErr) {
listener.stopWatching();
reject(blockErr);
return;
}

if (blockCounter <= 0) {
listener.stopWatching();
console.warn(`${ txHash } not mined in last ${ blockLimit } blocks`); // eslint-disable-line no-console

reject("blockLimit reached");
return;
}

web3.eth.getTransactionReceipt(txHash, (receiptErr, receiptRes) => {
blockCounter -= 1;

if (receiptRes) {
listener.stopWatching();
resolve(receiptRes);
}
});
Expand Down
2 changes: 2 additions & 0 deletions dapp/js/components/App.jsx
Expand Up @@ -6,11 +6,13 @@ import { Grid, Row, Col } from "react-bootstrap";

import Navigation from "../containers/Navigation";
import NoAccountWarning from "./NoAccountWarning";
import NotificationListContainer from "../containers/NotificationListContainer";

const App = props => (
<div className="app">
<Navigation />

<NotificationListContainer />
<Grid>
<Row>
<Col>
Expand Down
32 changes: 32 additions & 0 deletions dapp/js/components/NotificationList.jsx
@@ -0,0 +1,32 @@
import React from "react";
import PropTypes from "prop-types";
import { AlertList } from "react-bs-notifier";

const NotificationList = props => (
<AlertList
alerts={props.notifications}
onDismiss={props.onDismiss}
position={props.position}
/>
);

NotificationList.propTypes = {
onDismiss: PropTypes.func.isRequired,
notifications: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]).isRequired,
message: PropTypes.string.isRequired,
headline: PropTypes.string,
type: PropTypes.oneOf([ "info", "warning", "danger", "success" ]),
})).isRequired,
position: PropTypes.string,
};

NotificationList.defaultProps = {
notifications: [],
position: "top-right",
};

export default NotificationList;
5 changes: 5 additions & 0 deletions dapp/js/constants.js
Expand Up @@ -18,3 +18,8 @@ export const deploymentActions = {
export const userActions = {
SET_ACCOUNT: "SET_ACCOUNT",
};
export const transactionActions = {
NEW_TRANSATION: "NEW_TRANSACTION",
TRANSACTION_MINED: "TRANSACTION_MINED",
DISMISS_TRANSACTION: "DISMISS_TRANSACTION",
};
95 changes: 95 additions & 0 deletions dapp/js/containers/NotificationListContainer.jsx
@@ -0,0 +1,95 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import NotificationList from "../components/NotificationList";
import { dismissTransaction } from "../actions/transactions";
import { network } from "../blockchain";

class NotificationListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: this.getNotifications(),
};
this.dismissNotification = this.dismissNotification.bind(this);
}

componentWillReceiveProps(nextProps) {
if (this.props === nextProps) return;

this.props = nextProps;
this.setState(() => ({ notifications: this.getNotifications() }));
}

getNotifications() {
const notifications = [];
const wordBreak = "break-all";

this.props.transactions.forEach((tx) => {
const etherscanLink = (
<a
href={`${ network.etherscan }tx/${ tx.hash }`}
target="_blank"
rel="noopener noreferrer"
style={{ wordBreak }}
>
{tx.hash}
</a >
);

let msg;
if (tx.mined) {
msg = (
<div >
Transaction {etherscanLink} has been successfully mined!
</div >
);
} else {
msg = (
<div >
Transaction {etherscanLink} is being added to the Ethereum blockchain
</div >
);
}

notifications.push({
id: tx.hash,
message: msg,
type: tx.mined ? "success" : "info",
});
});

return notifications;
}

dismissNotification(notification) {
this.props.dismissTransaction(notification.id);
}

render() {
return (
<NotificationList
onDismiss={this.dismissNotification}
notifications={this.state.notifications}
/>
);
}
}

NotificationListContainer.propTypes = {
dismissTransaction: PropTypes.func.isRequired,
transactions: PropTypes.arrayOf(PropTypes.shape({
hash: PropTypes.string.isRequired,
mined: PropTypes.bool.isRequired,
})).isRequired,
};

const mapStateToProps = state => ({
transactions: state.transactions,
});

const mapDispatchToProps = ({
dismissTransaction,
});

export default connect(mapStateToProps, mapDispatchToProps)(NotificationListContainer);
2 changes: 2 additions & 0 deletions dapp/js/reducers/index.js
Expand Up @@ -2,6 +2,7 @@ import { combineReducers } from "redux";
import web3 from "./web3";
import givethDirectory from "./givethdirectory";
import newMilestones from "./newMilestones";
import transactions from "./transactions";
import {
campaignValues,
deploymentResults,
Expand All @@ -23,6 +24,7 @@ const reducers = combineReducers({
web3,
givethDirectory,
newMilestones,
transactions,
});

export default reducers;
43 changes: 43 additions & 0 deletions dapp/js/reducers/transactions.js
@@ -0,0 +1,43 @@
import { transactionActions } from "../constants";

const transactions = (state = [], action) => {
switch (action.type) {

case transactionActions.NEW_TRANSATION: {
return [
...state,
{
hash: action.hash,
mined: false,
},
];
}
case transactionActions.TRANSACTION_MINED: {
return state.map((tx) => {
if (tx.hash !== action.hash) {
return tx;
}

return Object.assign(tx, { mined: true });
});
}
case transactionActions.DISMISS_TRANSACTION: {
const i = state.findIndex(tx => tx.hash === action.hash);

if (i > -1) {
return [
...state.slice(0, i),
...state.slice(i + 1),
];
}

return state;
}
default: {
return state;
}

}
};

export default transactions;
5 changes: 5 additions & 0 deletions dapp/static/style.css
Expand Up @@ -77,3 +77,8 @@ CAMPAIGN DEPLOYER STYLES
.next-steps {
margin-top: 20px;
}

/* bs-notifications*/
.body-0-19 {
max-width: 42em;
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -71,6 +71,7 @@
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-bootstrap": "^0.31.0",
"react-bs-notifier": "^4.3.2",
"react-datetime": "^2.8.10",
"react-dom": "^15.6.1",
"react-file-download": "^0.3.4",
Expand Down
2 changes: 1 addition & 1 deletion webpack.config.js
Expand Up @@ -82,6 +82,6 @@ module.exports = {
]),
],
resolve: {
extensions: [ ".js", ".jsx" ],
extensions: [ ".js", ".jsx", ".json" ],
},
};

0 comments on commit fc07a92

Please sign in to comment.