From 930dac110aa9127380673e119b0eaab9d45b1198 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 25 Aug 2018 18:00:38 -0700 Subject: [PATCH] Add ActivityLog component --- app/_locales/en/messages.json | 3 + ui/app/components/card/card.component.js | 25 +++++ ui/app/components/card/index.js | 1 + ui/app/components/card/index.scss | 11 +++ ui/app/components/index.scss | 4 + .../components/sender-to-recipient/index.scss | 1 - .../transaction-activity-log/index.js | 1 + .../transaction-activity-log/index.scss | 53 +++++++++++ .../transaction-activity-log.component.js | 91 +++++++++++++++++++ .../transaction-activity-log.util.js | 75 +++++++++++++++ 10 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 ui/app/components/card/card.component.js create mode 100644 ui/app/components/card/index.js create mode 100644 ui/app/components/card/index.scss create mode 100644 ui/app/components/transaction-activity-log/index.js create mode 100644 ui/app/components/transaction-activity-log/index.scss create mode 100644 ui/app/components/transaction-activity-log/transaction-activity-log.component.js create mode 100644 ui/app/components/transaction-activity-log/transaction-activity-log.util.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 14e867b33..ad276306f 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -17,6 +17,9 @@ "accountSelectionRequired": { "message": "You need to select an account!" }, + "activityLog": { + "message": "activity log" + }, "address": { "message": "Address" }, diff --git a/ui/app/components/card/card.component.js b/ui/app/components/card/card.component.js new file mode 100644 index 000000000..bb7241da1 --- /dev/null +++ b/ui/app/components/card/card.component.js @@ -0,0 +1,25 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +export default class Card extends PureComponent { + static propTypes = { + className: PropTypes.string, + overrideClassName: PropTypes.bool, + title: PropTypes.string, + children: PropTypes.node, + } + + render () { + const { className, overrideClassName, title } = this.props + + return ( +
+
+ { title } +
+ { this.props.children } +
+ ) + } +} diff --git a/ui/app/components/card/index.js b/ui/app/components/card/index.js new file mode 100644 index 000000000..c3ca6e3f4 --- /dev/null +++ b/ui/app/components/card/index.js @@ -0,0 +1 @@ +export { default } from './card.component' diff --git a/ui/app/components/card/index.scss b/ui/app/components/card/index.scss new file mode 100644 index 000000000..68d972709 --- /dev/null +++ b/ui/app/components/card/index.scss @@ -0,0 +1,11 @@ +.card { + border-radius: 4px; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08); + padding: 16px 8px; + + &__title { + border-bottom: 1px solid #d8d8d8; + padding-bottom: 4px; + text-transform: capitalize; + } +} diff --git a/ui/app/components/index.scss b/ui/app/components/index.scss index cb4065fd9..e39dfe091 100644 --- a/ui/app/components/index.scss +++ b/ui/app/components/index.scss @@ -2,6 +2,8 @@ @import './button-group/index'; +@import './card/index'; + @import './confirm-page-container/index'; @import './export-text-container/index'; @@ -24,6 +26,8 @@ @import './tabs/index'; +@import './transaction-activity-log/index'; + @import './transaction-view/index'; @import './transaction-view-balance/index'; diff --git a/ui/app/components/sender-to-recipient/index.scss b/ui/app/components/sender-to-recipient/index.scss index 656e30ddf..6f128f729 100644 --- a/ui/app/components/sender-to-recipient/index.scss +++ b/ui/app/components/sender-to-recipient/index.scss @@ -80,7 +80,6 @@ justify-content: center; position: relative; flex: 0 0 auto; - padding: 8px; .sender-to-recipient { &__party { diff --git a/ui/app/components/transaction-activity-log/index.js b/ui/app/components/transaction-activity-log/index.js new file mode 100644 index 000000000..f39f8098c --- /dev/null +++ b/ui/app/components/transaction-activity-log/index.js @@ -0,0 +1 @@ +export { default } from './transaction-activity-log.component' diff --git a/ui/app/components/transaction-activity-log/index.scss b/ui/app/components/transaction-activity-log/index.scss new file mode 100644 index 000000000..fb24b77e2 --- /dev/null +++ b/ui/app/components/transaction-activity-log/index.scss @@ -0,0 +1,53 @@ +.transaction-activity-log { + &__card { + background: $white; + } + + &__activities-container { + padding-top: 8px; + } + + &__activity { + padding: 4px 0; + display: flex; + flex-direction: row; + align-items: center; + position: relative; + + &::after { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 6px; + border-right: 1px solid $scorpion; + } + + &:first-child::after { + height: 50%; + top: 50%; + } + + &:last-child::after { + height: 50%; + } + } + + &__activity-icon { + width: 13px; + height: 13px; + margin-right: 6px; + border-radius: 50%; + background: $scorpion; + } + + &__activity-text { + color: $scorpion; + font-size: .75rem; + } + + b { + font-weight: 500; + } +} diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.component.js b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js new file mode 100644 index 000000000..4cba8cf15 --- /dev/null +++ b/ui/app/components/transaction-activity-log/transaction-activity-log.component.js @@ -0,0 +1,91 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { getActivities } from './transaction-activity-log.util' +import Card from '../card' + +export default class TransactionActivityLog extends PureComponent { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + transaction: PropTypes.object, + } + + state = { + activities: [], + } + + componentDidMount () { + this.setActivites() + } + + componentDidUpdate (prevProps) { + const { transaction: { history: prevHistory = [] } = {} } = prevProps + const { transaction: { history = [] } = {} } = this.props + + if (prevHistory.length !== history.length) { + this.setActivites() + } + } + + setActivites () { + const activities = getActivities(this.props.transaction) + this.setState({ activities }) + } + + renderActivity (activity, index) { + return ( +
+
+ { this.renderActivityText(activity) } +
+ ) + } + + renderActivityText (activity) { + const { eventKey, value, valueDescriptionKey } = activity + + return ( +
+ { `Transaction ` } + { `${eventKey}` } + { + valueDescriptionKey && value + ? ( + + { ` with a ${valueDescriptionKey} of ` } + { value } + . + + ) : '.' + } +
+ ) + } + + render () { + const { t } = this.context + const { activities } = this.state + + return ( +
+ +
+ { + activities.map((activity, index) => ( + this.renderActivity(activity, index) + )) + } +
+
+
+ ) + } +} diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js new file mode 100644 index 000000000..fe780788a --- /dev/null +++ b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js @@ -0,0 +1,75 @@ +// path constants +const STATUS_PATH = '/status' +const GAS_PRICE_PATH = '/txParams/gasPrice' + +// status constants +const STATUS_UNAPPROVED = 'unapproved' +const STATUS_SUBMITTED = 'submitted' +const STATUS_CONFIRMED = 'confirmed' +const STATUS_DROPPED = 'dropped' + +// op constants +const REPLACE_OP = 'replace' + +const eventPathsHash = { + [STATUS_PATH]: true, + [GAS_PRICE_PATH]: true, +} + +const statusHash = { + [STATUS_SUBMITTED]: true, + [STATUS_CONFIRMED]: true, + [STATUS_DROPPED]: true, +} + +function eventCreator (eventKey, timestamp, value, valueDescriptionKey) { + return { + eventKey, + timestamp, + value, + valueDescriptionKey, + } +} + +export function getActivities (transaction) { + const { history = [] } = transaction + + return history.reduce((acc, base) => { + // First history item should be transaction creation + if (!Array.isArray(base) && base.status === STATUS_UNAPPROVED && base.txParams) { + const { time, txParams: { value } = {} } = base + return acc.concat(eventCreator('created', time, value, 'value')) + } else if (Array.isArray(base)) { + const events = [] + + base.forEach(entry => { + const { op, path, value, timestamp } = entry + + if (path in eventPathsHash && op === REPLACE_OP) { + switch (path) { + case STATUS_PATH: { + if (value in statusHash) { + events.push(eventCreator(value, timestamp)) + } + + break + } + + case GAS_PRICE_PATH: { + events.push(eventCreator('updated', timestamp, value, 'gasPrice')) + break + } + + default: { + events.push(eventCreator(value, timestamp)) + } + } + } + }) + + return acc.concat(events) + } + + return acc + }, []) +}