From 2b34d76b8ca02347e0bb8f2851c42c7805853d70 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 12:54:13 +0100 Subject: [PATCH 01/23] pull out fetchMeta --- js/src/contracts/badgereg.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 45760b277..02d08e516 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -38,16 +38,9 @@ export default class BadgeReg { .then((badgeReg) => { return badgeReg.instance.fromName.call({}, [name]) .then(([ id, address ]) => { - return Promise.all([ - badgeReg.instance.meta.call({}, [id, 'TITLE']), - badgeReg.instance.meta.call({}, [id, 'IMG']) - ]) - .then(([ title, img ]) => { - title = bytesToHex(title); - title = title === ZERO ? null : hex2Ascii(title); - if (bytesToHex(img) === ZERO) img = null; - - const data = { address, name, title, icon: img }; + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, name, title, icon }; this.certifiers[name] = data; return data; }); @@ -55,6 +48,22 @@ export default class BadgeReg { }); } + fetchMeta (id) { + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return Promise.all([ + badgeReg.instance.meta.call({}, [id, 'TITLE']), + badgeReg.instance.meta.call({}, [id, 'IMG']) + ]); + }) + .then(([ title, icon ]) => { + title = bytesToHex(title); + title = title === ZERO ? null : hex2Ascii(title); + if (bytesToHex(icon) === ZERO) icon = null; + return { title, icon }; + }); + } + checkIfCertified (certifier, address) { if (!this.contracts[certifier]) { this.contracts[certifier] = this._api.newContract(ABI, certifier); From b32b636697dac347b2d9de259d6c97ccd6acbd46 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 13:07:27 +0100 Subject: [PATCH 02/23] fetch certifiers by id --- js/src/contracts/badgereg.js | 44 ++++++++++++------- .../providers/certifications/middleware.js | 29 +++++++++--- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 02d08e516..45f7df315 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -18,7 +18,8 @@ import { bytesToHex, hex2Ascii } from '~/api/util/format'; import ABI from './abi/certifier.json'; -const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const ZERO20 = '0x0000000000000000000000000000000000000000'; +const ZERO32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; export default class BadgeReg { constructor (api, registry) { @@ -26,25 +27,36 @@ export default class BadgeReg { this._registry = registry; registry.getContract('badgereg'); - this.certifiers = {}; // by name + this.certifiers = []; // by id this.contracts = {}; // by name } - fetchCertifier (name) { - if (this.certifiers[name]) { - return Promise.resolve(this.certifiers[name]); + nrOfCertifiers () { + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return badgeReg.instance.badgeCount.call({}, []) + .then((count) => count.valueOf()); + }); + } + + fetchCertifier (id) { + if (this.certifiers[id]) { + return Promise.resolve(this.certifiers[id]); } return this._registry.getContract('badgereg') .then((badgeReg) => { - return badgeReg.instance.fromName.call({}, [name]) - .then(([ id, address ]) => { - return this.fetchMeta(id) - .then(({ title, icon }) => { - const data = { address, name, title, icon }; - this.certifiers[name] = data; - return data; - }); - }); + return badgeReg.instance.badge.call({}, [ id ]); + }) + .then(([ address, name ]) => { + if (address === ZERO20) throw new Error(`Certifier ${id} does not exist.`); + name = bytesToHex(name); + name = name === ZERO32 ? null : hex2Ascii(name); + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, name, title, icon }; + this.certifiers[id] = data; + return data; + }); }); } @@ -58,8 +70,8 @@ export default class BadgeReg { }) .then(([ title, icon ]) => { title = bytesToHex(title); - title = title === ZERO ? null : hex2Ascii(title); - if (bytesToHex(icon) === ZERO) icon = null; + title = title === ZERO32 ? null : hex2Ascii(title); + if (bytesToHex(icon) === ZERO32) icon = null; return { title, icon }; }); } diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index a5406051f..6c443cea5 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -17,20 +17,39 @@ import Contracts from '~/contracts'; import { addCertification } from './actions'; -const knownCertifiers = [ 'smsverification' ]; +const knownCertifiers = [ + 0 // sms verification +]; export default class CertificationsMiddleware { toMiddleware () { return (store) => (next) => (action) => { - if (action.type !== 'fetchCertifications') { + if (action.type === 'fetchCertifiers') { + badgeReg.nrOfCertifiers().then((count) => { + new Array(+count).fill(null).forEach((_, id) => { + badgeReg.fetchCertifier(id) + .then((cert) => { + const { address, name, title, icon } = cert; + store.dispatch(addCertifier(address, name, title, icon)); + }) + .catch((err) => { + if (err) { + console.error(`Failed to fetch certifier ${id}:`, err); + } + }); + }); + }); + } + + else if (action.type !== 'fetchCertifications') { return next(action); } const { address } = action; const badgeReg = Contracts.get().badgeReg; - knownCertifiers.forEach((name) => { - badgeReg.fetchCertifier(name) + knownCertifiers.forEach((id) => { + badgeReg.fetchCertifier(id) .then((cert) => { return badgeReg.checkIfCertified(cert.address, address) .then((isCertified) => { @@ -42,7 +61,7 @@ export default class CertificationsMiddleware { }) .catch((err) => { if (err) { - console.error(`Failed to check if ${address} certified by ${name}:`, err); + console.error(`Failed to check if ${address} certified by ${id}:`, err); } }); }); From 409c4adfbfd2a396f46534d5098a7af738e85607 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 22:05:16 +0100 Subject: [PATCH 03/23] fetch certifiers from BadgeReg --- .../redux/providers/certifications/actions.js | 4 + .../providers/certifications/middleware.js | 84 +++++++++++-------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/js/src/redux/providers/certifications/actions.js b/js/src/redux/providers/certifications/actions.js index c84f7db55..a8cc43f03 100644 --- a/js/src/redux/providers/certifications/actions.js +++ b/js/src/redux/providers/certifications/actions.js @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +export const fetchCertifiers = () => ({ + type: 'fetchCertifiers' +}); + export const fetchCertifications = (address) => ({ type: 'fetchCertifications', address }); diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 6c443cea5..715a5cb59 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -14,57 +14,73 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { uniq } from 'lodash'; + +import ABI from '~/contracts/abi/certifier.json'; +import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { addCertification } from './actions'; -const knownCertifiers = [ - 0 // sms verification -]; - export default class CertificationsMiddleware { toMiddleware () { + const api = Contracts.get()._api; + const badgeReg = Contracts.get().badgeReg; + const contract = new Contract(api, ABI); + const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); + + let certifiers = []; + let accounts = []; // these are addresses + + const fetchConfirmedEvents = (dispatch) => { + if (certifiers.length === 0 || accounts.length === 0) return; + api.eth.getLogs({ + fromBlock: 0, + toBlock: 'latest', + address: certifiers.map((c) => c.address), + topics: [ Confirmed.signature, accounts ] + }) + .then((logs) => contract.parseEventLogs(logs)) + .then((logs) => { + logs.forEach((log) => { + const certifier = certifiers.find((c) => c.address === log.address); + if (!certifier) throw new Error(`Could not find certifier at ${log.address}.`); + const { name, title, icon } = certifier; + dispatch(addCertification(log.params.who.value, name, title, icon)); + }); + }) + .catch((err) => { + console.error('Failed to fetch Confirmed events:', err); + }); + }; + return (store) => (next) => (action) => { if (action.type === 'fetchCertifiers') { badgeReg.nrOfCertifiers().then((count) => { new Array(+count).fill(null).forEach((_, id) => { badgeReg.fetchCertifier(id) .then((cert) => { - const { address, name, title, icon } = cert; - store.dispatch(addCertifier(address, name, title, icon)); + if (!certifiers.some((c) => c.address === cert.address)) { + certifiers = certifiers.concat(cert); + fetchConfirmedEvents(store.dispatch); + } }) .catch((err) => { - if (err) { - console.error(`Failed to fetch certifier ${id}:`, err); - } + console.warn(`Could not fetch certifier ${id}:`, err); }); }); }); - } + } else if (action.type === 'fetchCertifications') { + const { address } = action; - else if (action.type !== 'fetchCertifications') { - return next(action); - } - - const { address } = action; - const badgeReg = Contracts.get().badgeReg; - - knownCertifiers.forEach((id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - return badgeReg.checkIfCertified(cert.address, address) - .then((isCertified) => { - if (isCertified) { - const { name, title, icon } = cert; - store.dispatch(addCertification(address, name, title, icon)); - } - }); - }) - .catch((err) => { - if (err) { - console.error(`Failed to check if ${address} certified by ${id}:`, err); - } - }); - }); + if (!accounts.includes(address)) { + accounts = accounts.concat(address); + fetchConfirmedEvents(store.dispatch); + } + } else if (action.type === 'setVisibleAccounts') { + const { addresses } = action; + accounts = uniq(accounts.concat(addresses)); + fetchConfirmedEvents(store.dispatch); + } else return next(action); }; } } From e1c5796a5c43643aab7075ffe2be984a2f4a6d3a Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 22:58:24 +0100 Subject: [PATCH 04/23] fetch certifications in account view --- js/src/ui/Certifications/certifications.js | 16 ++-------------- js/src/views/Account/account.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 871b14e9c..b034deda4 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -19,7 +19,6 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { hashToImageUrl } from '~/redux/providers/imagesReducer'; -import { fetchCertifications } from '~/redux/providers/certifications/actions'; import defaultIcon from '../../../assets/images/certifications/unknown.svg'; @@ -29,14 +28,7 @@ class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, certifications: PropTypes.array.isRequired, - dappsUrl: PropTypes.string.isRequired, - - fetchCertifications: PropTypes.func.isRequired - } - - componentWillMount () { - const { account, fetchCertifications } = this.props; - fetchCertifications(account); + dappsUrl: PropTypes.string.isRequired } render () { @@ -77,11 +69,7 @@ function mapStateToProps (_, initProps) { }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({ fetchCertifications }, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(Certifications); diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98b0a5e97..ec9551d6b 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -31,6 +31,7 @@ import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; +import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import VerificationStore from '~/modals/SMSVerification/store'; @@ -43,6 +44,8 @@ class Account extends Component { static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, + fetchCertifiers: PropTypes.func.isRequired, + fetchCertifications: PropTypes.func.isRequired, images: PropTypes.object.isRequired, params: PropTypes.object, @@ -62,6 +65,7 @@ class Account extends Component { } componentDidMount () { + this.props.fetchCertifiers(); this.setVisibleAccounts(); } @@ -88,9 +92,10 @@ class Account extends Component { } setVisibleAccounts (props = this.props) { - const { params, setVisibleAccounts } = props; + const { params, setVisibleAccounts, fetchCertifications } = props; const addresses = [ params.address ]; setVisibleAccounts(addresses); + fetchCertifications(params.address); } render () { @@ -344,7 +349,9 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return bindActionCreators({ - setVisibleAccounts + setVisibleAccounts, + fetchCertifiers, + fetchCertifications }, dispatch); } From 5862f2a9ebd2d72f2d639802de1c2d7b96f5b3b4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:30:05 +0100 Subject: [PATCH 05/23] Certifications: read dappsUrl from state --- js/src/ui/Certifications/certifications.js | 6 +++--- js/src/views/Account/Header/header.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index b034deda4..47cf36942 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -27,8 +27,7 @@ import styles from './certifications.css'; class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, - certifications: PropTypes.array.isRequired, - dappsUrl: PropTypes.string.isRequired + certifications: PropTypes.array.isRequired } render () { @@ -65,7 +64,8 @@ function mapStateToProps (_, initProps) { return (state) => { const certifications = state.certifications[account] || []; - return { certifications }; + const dappsUrl = state.api.dappsUrl; + return { certifications, dappsUrl }; }; } diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index b126951b2..c77c4987d 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -78,7 +78,6 @@ export default class Header extends Component { balance={ balance } /> { children } From a84cd9143f014c9bba9a5b2bc6c9790bf9d9f27b Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:34:28 +0100 Subject: [PATCH 06/23] show certifications in accounts list --- js/src/views/Accounts/List/list.js | 40 ++++++++++++++++++++++-- js/src/views/Accounts/Summary/summary.js | 17 +++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 4d54b640f..8c29d17f0 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -15,13 +15,16 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Container } from '~/ui'; +import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import Summary from '../Summary'; import styles from './list.css'; -export default class List extends Component { +class List extends Component { static propTypes = { accounts: PropTypes.object, walletsOwners: PropTypes.object, @@ -31,7 +34,11 @@ export default class List extends Component { empty: PropTypes.bool, order: PropTypes.string, orderFallback: PropTypes.string, - handleAddSearchToken: PropTypes.func + certifications: PropTypes.object.isRequired, + + handleAddSearchToken: PropTypes.func, + fetchCertifiers: PropTypes.func.isRequired, + fetchCertifications: PropTypes.func.isRequired }; render () { @@ -42,6 +49,14 @@ export default class List extends Component { ); } + componentWillMount () { + const { fetchCertifiers, accounts, fetchCertifications } = this.props; + fetchCertifiers(); + for (let address in accounts) { + fetchCertifications(address); + } + } + renderAccounts () { const { accounts, balances, link, empty, handleAddSearchToken, walletsOwners } = this.props; @@ -72,7 +87,9 @@ export default class List extends Component { account={ account } balance={ balance } owners={ owners } - handleAddSearchToken={ handleAddSearchToken } /> + handleAddSearchToken={ handleAddSearchToken } + showCertifications + /> ); }); @@ -207,3 +224,20 @@ export default class List extends Component { }); } } + +function mapStateToProps (state) { + const { certifications } = state; + return { certifications }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + fetchCertifiers, + fetchCertifications + }, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(List); diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 764f24edf..0894e8699 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -20,6 +20,7 @@ import { isEqual } from 'lodash'; import ReactTooltip from 'react-tooltip'; import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui'; +import Certifications from '~/ui/Certifications'; import { nullableProptype } from '~/util/proptypes'; import styles from '../accounts.css'; @@ -35,12 +36,14 @@ export default class Summary extends Component { link: PropTypes.string, name: PropTypes.string, noLink: PropTypes.bool, + showCertifications: PropTypes.bool, handleAddSearchToken: PropTypes.func, owners: nullableProptype(PropTypes.array) }; static defaultProps = { - noLink: false + noLink: false, + showCertifications: false }; shouldComponentUpdate (nextProps) { @@ -107,6 +110,7 @@ export default class Summary extends Component { { this.renderOwners() } { this.renderBalance() } + { this.renderCertifications() } ); } @@ -172,4 +176,15 @@ export default class Summary extends Component { ); } + + renderCertifications () { + const { showCertifications, account } = this.props; + if (!showCertifications) { + return null; + } + + return ( + + ); + } } From e53629089230f23899f73840e8b218f5d4062c8d Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:38:44 +0100 Subject: [PATCH 07/23] fix linting issues --- js/src/ui/Certifications/certifications.js | 4 ++-- js/src/views/Account/Header/header.js | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 47cf36942..36635ff58 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -16,7 +16,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { hashToImageUrl } from '~/redux/providers/imagesReducer'; @@ -27,7 +26,8 @@ import styles from './certifications.css'; class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, - certifications: PropTypes.array.isRequired + certifications: PropTypes.array.isRequired, + dappsUrl: PropTypes.string.isRequired } render () { diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index c77c4987d..c2b9151e7 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -23,10 +23,6 @@ import Certifications from '~/ui/Certifications'; import styles from './header.css'; export default class Header extends Component { - static contextTypes = { - api: PropTypes.object - }; - static propTypes = { account: PropTypes.object, balance: PropTypes.object, @@ -40,7 +36,6 @@ export default class Header extends Component { }; render () { - const { api } = this.context; const { account, balance, className, children } = this.props; const { address, meta, uuid } = account; From fd88421e88d71153e7917e4958264d62ea969e7a Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 13:35:10 +0100 Subject: [PATCH 08/23] store id with certifiers --- js/src/contracts/badgereg.js | 2 +- js/src/redux/providers/certifications/actions.js | 8 ++++++-- js/src/redux/providers/certifications/middleware.js | 6 +++--- js/src/redux/providers/certifications/reducer.js | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 45f7df315..629add3a4 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -53,7 +53,7 @@ export default class BadgeReg { name = name === ZERO32 ? null : hex2Ascii(name); return this.fetchMeta(id) .then(({ title, icon }) => { - const data = { address, name, title, icon }; + const data = { address, id, name, title, icon }; this.certifiers[id] = data; return data; }); diff --git a/js/src/redux/providers/certifications/actions.js b/js/src/redux/providers/certifications/actions.js index a8cc43f03..d0c2d56f4 100644 --- a/js/src/redux/providers/certifications/actions.js +++ b/js/src/redux/providers/certifications/actions.js @@ -22,6 +22,10 @@ export const fetchCertifications = (address) => ({ type: 'fetchCertifications', address }); -export const addCertification = (address, name, title, icon) => ({ - type: 'addCertification', address, name, title, icon +export const addCertification = (address, id, name, title, icon) => ({ + type: 'addCertification', address, id, name, title, icon +}); + +export const removeCertification = (address, id, name, title, icon) => ({ + type: 'removeCertification', address, id, name, title, icon }); diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 715a5cb59..d90045270 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -44,8 +44,8 @@ export default class CertificationsMiddleware { logs.forEach((log) => { const certifier = certifiers.find((c) => c.address === log.address); if (!certifier) throw new Error(`Could not find certifier at ${log.address}.`); - const { name, title, icon } = certifier; - dispatch(addCertification(log.params.who.value, name, title, icon)); + const { id, name, title, icon } = certifier; + dispatch(addCertification(log.params.who.value, id, name, title, icon)); }); }) .catch((err) => { @@ -59,7 +59,7 @@ export default class CertificationsMiddleware { new Array(+count).fill(null).forEach((_, id) => { badgeReg.fetchCertifier(id) .then((cert) => { - if (!certifiers.some((c) => c.address === cert.address)) { + if (!certifiers.some((c) => c.id === cert.id)) { certifiers = certifiers.concat(cert); fetchConfirmedEvents(store.dispatch); } diff --git a/js/src/redux/providers/certifications/reducer.js b/js/src/redux/providers/certifications/reducer.js index 87b0dfd33..df726be82 100644 --- a/js/src/redux/providers/certifications/reducer.js +++ b/js/src/redux/providers/certifications/reducer.js @@ -21,13 +21,13 @@ export default (state = initialState, action) => { return state; } - const { address, name, icon, title } = action; + const { address, id, name, icon, title } = action; const certifications = state[address] || []; - if (certifications.some((c) => c.name === name)) { + if (certifications.some((c) => c.id === id)) { return state; } - const newCertifications = certifications.concat({ name, icon, title }); + const newCertifications = certifications.concat({ id, name, icon, title }); return { ...state, [address]: newCertifications }; }; From 6d20592f761ac7b948dfa917e719d9805e472a3c Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 14:06:02 +0100 Subject: [PATCH 09/23] remove certification on Revoke --- .../redux/providers/certifications/actions.js | 4 ++-- .../providers/certifications/middleware.js | 11 ++++++--- .../redux/providers/certifications/reducer.js | 24 ++++++++++++------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/js/src/redux/providers/certifications/actions.js b/js/src/redux/providers/certifications/actions.js index d0c2d56f4..3638896b4 100644 --- a/js/src/redux/providers/certifications/actions.js +++ b/js/src/redux/providers/certifications/actions.js @@ -26,6 +26,6 @@ export const addCertification = (address, id, name, title, icon) => ({ type: 'addCertification', address, id, name, title, icon }); -export const removeCertification = (address, id, name, title, icon) => ({ - type: 'removeCertification', address, id, name, title, icon +export const removeCertification = (address, id) => ({ + type: 'removeCertification', address, id }); diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index d90045270..fe3d6b8df 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -19,7 +19,7 @@ import { uniq } from 'lodash'; import ABI from '~/contracts/abi/certifier.json'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; -import { addCertification } from './actions'; +import { addCertification, removeCertification } from './actions'; export default class CertificationsMiddleware { toMiddleware () { @@ -27,6 +27,7 @@ export default class CertificationsMiddleware { const badgeReg = Contracts.get().badgeReg; const contract = new Contract(api, ABI); const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); + const Revoked = contract.events.find((e) => e.name === 'Revoked'); let certifiers = []; let accounts = []; // these are addresses @@ -37,7 +38,7 @@ export default class CertificationsMiddleware { fromBlock: 0, toBlock: 'latest', address: certifiers.map((c) => c.address), - topics: [ Confirmed.signature, accounts ] + topics: [ [ Confirmed.signature, Revoked.signature ], accounts ] }) .then((logs) => contract.parseEventLogs(logs)) .then((logs) => { @@ -45,7 +46,11 @@ export default class CertificationsMiddleware { const certifier = certifiers.find((c) => c.address === log.address); if (!certifier) throw new Error(`Could not find certifier at ${log.address}.`); const { id, name, title, icon } = certifier; - dispatch(addCertification(log.params.who.value, id, name, title, icon)); + if (log.event === 'Revoked') { + dispatch(removeCertification(log.params.who.value, id)); + } else { + dispatch(addCertification(log.params.who.value, id, name, title, icon)); + } }); }) .catch((err) => { diff --git a/js/src/redux/providers/certifications/reducer.js b/js/src/redux/providers/certifications/reducer.js index df726be82..4014ca241 100644 --- a/js/src/redux/providers/certifications/reducer.js +++ b/js/src/redux/providers/certifications/reducer.js @@ -17,17 +17,25 @@ const initialState = {}; export default (state = initialState, action) => { - if (action.type !== 'addCertification') { - return state; + if (action.type === 'addCertification') { + const { address, id, name, icon, title } = action; + const certifications = state[address] || []; + + if (certifications.some((c) => c.id === id)) { + return state; + } + const newCertifications = certifications.concat({ id, name, icon, title }); + + return { ...state, [address]: newCertifications }; } - const { address, id, name, icon, title } = action; - const certifications = state[address] || []; + if (action.type === 'removeCertification') { + const { address, id } = action; + const certifications = state[address] || []; - if (certifications.some((c) => c.id === id)) { - return state; + const newCertifications = certifications.filter((c) => c.id !== id); + return { ...state, [address]: newCertifications }; } - const newCertifications = certifications.concat({ id, name, icon, title }); - return { ...state, [address]: newCertifications }; + return state; }; From 085adbaffd8a23f25c85d25adb4deb980e541318 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Mon, 12 Dec 2016 14:15:26 +0100 Subject: [PATCH 10/23] make showFirstRun in localStorage a bool --- js/src/views/Application/store.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js/src/views/Application/store.js b/js/src/views/Application/store.js index bb42721cd..b9f960eff 100644 --- a/js/src/views/Application/store.js +++ b/js/src/views/Application/store.js @@ -16,14 +16,17 @@ import { action, observable } from 'mobx'; -const showFirstRun = window.localStorage.getItem('showFirstRun') !== '0'; - export default class Store { - @observable firstrunVisible = showFirstRun; + @observable firstrunVisible = false; constructor (api) { this._api = api; + const value = window.localStorage.getItem('showFirstRun'); + if (value) { + this.firstrunVisible = JSON.parse(value); + } + this._checkAccounts(); } @@ -33,7 +36,7 @@ export default class Store { @action toggleFirstrun = (visible = false) => { this.firstrunVisible = visible; - window.localStorage.setItem('showFirstRun', visible ? '1' : '0'); + window.localStorage.setItem('showFirstRun', JSON.stringify(!!visible)); } _checkAccounts () { From b980e7a3297b9bf8f5e01355bf6e6eb835dfed15 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Mon, 12 Dec 2016 14:32:55 +0100 Subject: [PATCH 11/23] add skip button to first run modal --- js/src/modals/FirstRun/firstRun.js | 36 +++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/js/src/modals/FirstRun/firstRun.js b/js/src/modals/FirstRun/firstRun.js index a273d6e63..03bc8d770 100644 --- a/js/src/modals/FirstRun/firstRun.js +++ b/js/src/modals/FirstRun/firstRun.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; import ActionDone from 'material-ui/svg-icons/action/done'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; @@ -35,14 +36,15 @@ import ParityLogo from '../../../assets/images/parity-logo-black-no-text.svg'; const STAGE_NAMES = ['welcome', 'terms', 'new account', 'recovery', 'completed']; -export default class FirstRun extends Component { +class FirstRun extends Component { static contextTypes = { api: PropTypes.object.isRequired, store: PropTypes.object.isRequired } static propTypes = { - visible: PropTypes.bool, + hasAccounts: PropTypes.bool.isRequired, + visible: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired } @@ -109,6 +111,7 @@ export default class FirstRun extends Component { } renderDialogActions () { + const { hasAccounts } = this.props; const { canCreate, stage, hasAcceptedTnc } = this.state; switch (stage) { @@ -130,13 +133,26 @@ export default class FirstRun extends Component { ); case 2: - return ( + const buttons = [