diff --git a/package.json b/package.json index 89da6a29..642362cf 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "react-dom": "^16.8.2", "react-google-maps": "^9.4.5", "react-i18next": "^9.0.2", + "react-notifications": "^1.4.3", "react-redux": "^6.0.0", "react-router-dom": "^4.3.1", "react-scripts": "2.1.5", diff --git a/src/js/components/NotificationManager/index.jsx b/src/js/components/NotificationManager/index.jsx index 58e23ef0..86e0b92d 100644 --- a/src/js/components/NotificationManager/index.jsx +++ b/src/js/components/NotificationManager/index.jsx @@ -1,6 +1,8 @@ import React, {Component} from 'react'; import EscrowNotifications from './notifications/EscrowNotifications'; +import 'react-notifications/lib/notifications.css'; + class NotificationManager extends Component { render() { return ( diff --git a/src/js/components/NotificationManager/notifications/EscrowNotifications.jsx b/src/js/components/NotificationManager/notifications/EscrowNotifications.jsx index 711949a0..53764693 100644 --- a/src/js/components/NotificationManager/notifications/EscrowNotifications.jsx +++ b/src/js/components/NotificationManager/notifications/EscrowNotifications.jsx @@ -3,28 +3,34 @@ import {withRouter} from "react-router-dom"; import PropTypes from 'prop-types'; import {connect} from "react-redux"; import escrow from '../../../features/escrow'; +import {NotificationContainer, NotificationManager} from 'react-notifications'; const DELAY = 5000; class EscrowNotifications extends Component { componentDidUpdate(prevProps) { if (!prevProps.newEscrow && this.props.newEscrow) { + NotificationManager.info(`For Offer ${this.props.newEscrow.offerId}: ${this.props.newEscrow.token.symbol} → ${this.props.newEscrow.offer.currency}`, + 'New trade created', DELAY, () => { + clearTimeout(this.escrowNotifTimeout); + this.props.clearNewEscrow(); + this.props.history.push(`/escrow/${this.props.newEscrow.escrowId}`); + }); this.escrowNotifTimeout = setTimeout(() => { - console.log('Remove this'); + this.props.clearNewEscrow(); }, DELAY); } } render() { - if (this.props.newEscrow) { - console.log('NEW ESCrow', this.props.newEscrow); - } - return null; + return ; } } EscrowNotifications.propTypes = { - newEscrow: PropTypes.object + newEscrow: PropTypes.object, + history: PropTypes.object, + clearNewEscrow: PropTypes.func }; @@ -38,5 +44,6 @@ const mapStateToProps = (state) => { export default connect( mapStateToProps, { + clearNewEscrow: escrow.actions.clearNewEscrow } )(withRouter(EscrowNotifications)); diff --git a/src/js/features/escrow/actions.js b/src/js/features/escrow/actions.js index 14c1a972..6622e709 100644 --- a/src/js/features/escrow/actions.js +++ b/src/js/features/escrow/actions.js @@ -3,7 +3,7 @@ import { CREATE_ESCROW, LOAD_ESCROWS, RELEASE_ESCROW, CANCEL_ESCROW, RATE_TRANSACTION, PAY_ESCROW, OPEN_CASE, OPEN_CASE_SIGNATURE, PAY_ESCROW_SIGNATURE, CLOSE_DIALOG, ADD_USER_RATING, USER_RATING, GET_ESCROW, GET_FEE, FUND_ESCROW, RESET_STATUS, - WATCH_ESCROW, WATCH_ESCROW_CREATIONS + WATCH_ESCROW, WATCH_ESCROW_CREATIONS, CLEAR_NEW_ESCROW } from './constants'; import Escrow from '../../../embarkArtifacts/contracts/Escrow'; @@ -84,6 +84,8 @@ export const resetStatus = () => ({type: RESET_STATUS}); export const watchEscrow = (escrowId) => ({type: WATCH_ESCROW, escrowId}); export const watchEscrowCreations = (offers) => ({type: WATCH_ESCROW_CREATIONS, offers}); +export const clearNewEscrow = () => ({type: CLEAR_NEW_ESCROW}); + // TODO: Update with new UI export const payEscrowSignature = (escrowId) => ({ type: PAY_ESCROW_SIGNATURE, escrowId }); diff --git a/src/js/features/escrow/constants.js b/src/js/features/escrow/constants.js index 52c56d9d..d1eef469 100644 --- a/src/js/features/escrow/constants.js +++ b/src/js/features/escrow/constants.js @@ -11,6 +11,8 @@ export const ESCROW_EVENT_RECEIVED = 'ESCROW_EVENT_RECEIVED'; export const WATCH_ESCROW_CREATIONS = 'WATCH_ESCROW_CREATIONS'; export const ESCROW_CREATED_EVENT_RECEIVED = 'ESCROW_CREATED_EVENT_RECEIVED'; +export const CLEAR_NEW_ESCROW = 'CLEAR_NEW_ESCROW'; + export const GET_ESCROW = 'GET_ESCROW'; export const GET_ESCROW_SUCCEEDED = 'GET_ESCROW_SUCCEEDED'; export const GET_ESCROW_FAILED = 'GET_ESCROW_FAILED'; diff --git a/src/js/features/escrow/reducer.js b/src/js/features/escrow/reducer.js index ce1398c1..4881a0eb 100644 --- a/src/js/features/escrow/reducer.js +++ b/src/js/features/escrow/reducer.js @@ -9,7 +9,7 @@ import { PAY_ESCROW, PAY_ESCROW_SUCCEEDED, PAY_ESCROW_FAILED, PAY_ESCROW_PRE_SUCCESS, CANCEL_ESCROW, CANCEL_ESCROW_SUCCEEDED, CANCEL_ESCROW_FAILED, CANCEL_ESCROW_PRE_SUCCESS, RATE_TRANSACTION, RATE_TRANSACTION_FAILED, RATE_TRANSACTION_SUCCEEDED, RATE_TRANSACTION_PRE_SUCCESS, - ESCROW_EVENT_RECEIVED, ESCROW_CREATED_EVENT_RECEIVED + ESCROW_EVENT_RECEIVED, ESCROW_CREATED_EVENT_RECEIVED, CLEAR_NEW_ESCROW } from './constants'; import { States } from '../../utils/transaction'; import { escrowStatus, eventTypes } from './helpers'; @@ -235,6 +235,11 @@ function reducer(state = DEFAULT_STATE, action) { ...state, newEscrow: action.result.returnValues.escrowId }; + case CLEAR_NEW_ESCROW: + return { + ...state, + newEscrow: null + }; case RESET_STATUS: return { ...state, diff --git a/src/js/features/escrow/saga.js b/src/js/features/escrow/saga.js index e00d4d87..5d8da2af 100644 --- a/src/js/features/escrow/saga.js +++ b/src/js/features/escrow/saga.js @@ -277,7 +277,7 @@ export function *onWatchEscrow() { export function *watchEscrowCreations({offers}) { try { - yield all(offers.map(offer => contractEvent(Escrow, eventTypes.created, {offerId: offer.offerId}, ESCROW_CREATED_EVENT_RECEIVED))); + yield all(offers.map(offer => contractEvent(Escrow, eventTypes.created, {offerId: offer.id}, ESCROW_CREATED_EVENT_RECEIVED))); } catch (error) { console.error(error); } diff --git a/src/js/layout/App.jsx b/src/js/layout/App.jsx index d446e42b..d922737a 100644 --- a/src/js/layout/App.jsx +++ b/src/js/layout/App.jsx @@ -109,9 +109,9 @@ class App extends Component { return ( - +
diff --git a/src/js/utils/saga.js b/src/js/utils/saga.js index 2b6ffb45..8db019b0 100644 --- a/src/js/utils/saga.js +++ b/src/js/utils/saga.js @@ -48,7 +48,7 @@ export function *doTransaction(preSuccess, success, failed, {value = 0, toSend}) } } -export function contractEventChannel(contract, event, filter, emitter) { +export function contractOnceEventChannel(contract, event, filter, emitter) { contract.once(event, {filter}, (err, result) => { if (err) { emitter({err}); @@ -60,8 +60,28 @@ export function contractEventChannel(contract, event, filter, emitter) { return () => {}; } -export function *contractEvent(contract, event, filter, successType) { - const channel = eventChannel(contractEventChannel.bind(null, contract, event, filter)); +export function contractEventChannel(contract, event, filter, emitter) { + const sub = contract.events[event]({ + filter + }, (err, result) => { + if (err) { + emitter({err}); + return emitter(END); + } + emitter({result}); + }); + return () => { + sub.unsubscribe(); + }; +} + +export function *contractEvent(contract, event, filter, successType, perpetualEvent) { + let channel; + if (perpetualEvent) { + channel = eventChannel(contractEventChannel.bind(null, contract, event, filter)); + } else { + channel = eventChannel(contractOnceEventChannel.bind(null, contract, event, filter)); + } while (true) { const {result, error} = yield take(channel); if (result) { diff --git a/yarn.lock b/yarn.lock index 406cb081..63d1b251 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4392,6 +4392,11 @@ ccount@^1.0.3: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw== +chain-function@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc" + integrity sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg== + chalk@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" @@ -4603,7 +4608,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: +classnames@^2.1.1, classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -6022,7 +6027,7 @@ dom-converter@^0.2: dependencies: utila "~0.4" -dom-helpers@^3.2.1, dom-helpers@^3.4.0: +dom-helpers@^3.2.0, dom-helpers@^3.2.1, dom-helpers@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== @@ -14104,7 +14109,7 @@ prop-types-extra@^1.0.1: react-is "^16.3.2" warning "^3.0.0" -prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -14729,6 +14734,15 @@ react-modal@^3.6.1: react-lifecycles-compat "^3.0.0" warning "^3.0.0" +react-notifications@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/react-notifications/-/react-notifications-1.4.3.tgz#7060d339896f125a5b183ebcd04526980a433222" + integrity sha1-cGDTOYlvElpbGD680EUmmApDMiI= + dependencies: + classnames "^2.1.1" + prop-types "^15.5.10" + react-transition-group "^1.2.0" + react-onclickoutside@^6.5.0: version "6.8.0" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.8.0.tgz#9f91b5b3ed59f4d9e43fd71620dc200773a4d569" @@ -14892,6 +14906,17 @@ react-textarea-autosize@^7.0.4: "@babel/runtime" "^7.1.2" prop-types "^15.6.0" +react-transition-group@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6" + integrity sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q== + dependencies: + chain-function "^1.0.0" + dom-helpers "^3.2.0" + loose-envify "^1.3.1" + prop-types "^15.5.6" + warning "^3.0.0" + react-transition-group@^2.0.0, react-transition-group@^2.2.0, react-transition-group@^2.3.1: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"