diff --git a/README.md b/README.md index 8e26c20..31fd851 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ We use the yarn package manager instead of the default `npm`. There are multiple ```bash npm install -g yarn yarn global add babel-cli -yarn install +yarn install --frozen-lockfile yarn run build ``` To run condenser in production mode, run: diff --git a/src/app/components/App.scss b/src/app/components/App.scss index b54392e..ddf2f5c 100644 --- a/src/app/components/App.scss +++ b/src/app/components/App.scss @@ -1,3 +1,5 @@ +@import "./cards/TransferHistoryRow"; + .App { min-height: 100vh; padding-top: 50px; diff --git a/src/app/components/cards/TransferHistoryRow.jsx b/src/app/components/cards/TransferHistoryRow.jsx index 705db0f..93ede5e 100644 --- a/src/app/components/cards/TransferHistoryRow.jsx +++ b/src/app/components/cards/TransferHistoryRow.jsx @@ -1,11 +1,12 @@ import React from 'react'; import {connect} from 'react-redux' import {Link} from 'react-router'; +import tt from 'counterpart'; import TimeAgoWrapper from '../elements/TimeAgoWrapper'; // import Icon from '../elements/Icon'; import Memo from '../elements/Memo' import {numberWithCommas, vestsToSp} from '../../utils/StateFunctions' -import tt from 'counterpart'; +import BadActorList from '../../utils/BadActorList'; class TransferHistoryRow extends React.Component { render() { @@ -110,7 +111,13 @@ class TransferHistoryRow extends React.Component { {description_end} - + -1 + } + /> ); diff --git a/src/app/components/cards/TransferHistoryRow.scss b/src/app/components/cards/TransferHistoryRow.scss new file mode 100644 index 0000000..eb3ee8b --- /dev/null +++ b/src/app/components/cards/TransferHistoryRow.scss @@ -0,0 +1,19 @@ +.Memo--badActor { + .bad-actor-caution { + color: darken($color-red, 20%); + font-size: 75%; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 1px; + } + .bad-actor-explained { + opacity: .5; + font-size: 75%; + max-width: 30em; + } + .bad-actor-reveal-memo { + opacity: .5; + font-size: 75%; + text-decoration: underline; + } +} diff --git a/src/app/components/elements/Memo.js b/src/app/components/elements/Memo.js index 558f4ac..e6baa94 100644 --- a/src/app/components/elements/Memo.js +++ b/src/app/components/elements/Memo.js @@ -3,40 +3,84 @@ import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import shouldComponentUpdate from '../../utils/shouldComponentUpdate'; import tt from 'counterpart'; +import classnames from 'classnames'; import {memo} from '@smokenetwork/smoke-js'; class Memo extends React.Component { static propTypes = { text: PropTypes.string, // username: PropTypes.string, - memo_private: PropTypes.object, + username: PropTypes.string, + isFromBadActor: PropTypes.bool.isRequired, // redux props myAccount: PropTypes.bool, + memo_private: PropTypes.object, } constructor() { super() this.shouldComponentUpdate = shouldComponentUpdate(this, 'Memo'); - this.decodeMemo = (memo_private, text) => { - try { - return memo.decode(memo_private, text) - } catch (e) { - // if(/Invalid key/i.test(e.toString())) { + this.state = { + revealBadActorMemo: false, + } + } + + decodeMemo(memo_private, text) { + try { + return memo.decode(memo_private, text); + } catch (e) { console.error('memo decryption error', text, e); - return 'Invalid memo' - } + return 'Invalid memo'; } } + onRevealBadActorMemo = e => { + e.preventDefault(); + this.setState({ revealBadActorMemo: true }); + }; + render() { const {decodeMemo} = this - const {memo_private, text, myAccount} = this.props; + const { memo_private, text, myAccount, isFromBadActor } = this.props; const isEncoded = /^#/.test(text); - if (!isEncoded) return {text} - if (!myAccount) return - if (memo_private) return {decodeMemo(memo_private, text)} - return {tt('g.login_to_see_memo')} + const classes = classnames({ + Memo: true, + 'Memo--badActor': isFromBadActor, + 'Memo--private': memo_private, + }); + + let renderText = ''; + + if (!isEncoded) { + renderText = text; + } else if (memo_private) { + renderText = myAccount + ? decodeMemo(memo_private, text) + : tt('g.login_to_see_memo'); + } + + if (isFromBadActor && !this.state.revealBadActorMemo) { + renderText = ( +
+
+ {tt('transferhistoryrow_jsx.bad_actor_caution')} +
+
+ {tt('transferhistoryrow_jsx.bad_actor_explained')} +
+
+ {tt('transferhistoryrow_jsx.bad_actor_reveal_memo')} +
+
+ ); + } + + return {renderText}; } } diff --git a/src/app/locales/en.json b/src/app/locales/en.json index dd55f99..63b4bbc 100644 --- a/src/app/locales/en.json +++ b/src/app/locales/en.json @@ -491,7 +491,10 @@ "transferhistoryrow_jsx": { "stop_power_down": "Stop power down", "start_power_down_of": "Start power down of", - "receive_interest_of": "Receive interest of" + "receive_interest_of": "Receive interest of", + "bad_actor_caution": "Caution", + "bad_actor_explained": "This is from an account that may be malicious. If you wish, you may temporarily reveal this memo which may contain dangerous links.", + "bad_actor_reveal_memo": "I understand the risk; show anyway." }, "explorepost_jsx": { "copied": "Copied!",