diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index f86498776..beef994aa 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -1090,7 +1090,7 @@
"description": "For importing an account from a private key"
},
"pending": {
- "message": "pending"
+ "message": "Pending"
},
"permissionCheckedIconDescription": {
"message": "You have approved this permission"
diff --git a/ui/app/components/app/transaction-icon/index.js b/ui/app/components/app/transaction-icon/index.js
new file mode 100644
index 000000000..d41970a54
--- /dev/null
+++ b/ui/app/components/app/transaction-icon/index.js
@@ -0,0 +1 @@
+export { default } from './transaction-icon'
diff --git a/ui/app/components/app/transaction-icon/transaction-icon.js b/ui/app/components/app/transaction-icon/transaction-icon.js
new file mode 100644
index 000000000..24f4f4715
--- /dev/null
+++ b/ui/app/components/app/transaction-icon/transaction-icon.js
@@ -0,0 +1,58 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import Approve from '../../ui/icon/approve-icon.component'
+import Interaction from '../../ui/icon/interaction-icon.component'
+import Receive from '../../ui/icon/receive-icon.component'
+import Send from '../../ui/icon/send-icon.component'
+import Sign from '../../ui/icon/sign-icon.component'
+import {
+ TRANSACTION_CATEGORY_APPROVAL,
+ TRANSACTION_CATEGORY_SIGNATURE_REQUEST,
+ TRANSACTION_CATEGORY_INTERACTION,
+ TRANSACTION_CATEGORY_SEND,
+ TRANSACTION_CATEGORY_RECEIVE,
+ UNAPPROVED_STATUS,
+ FAILED_STATUS,
+ REJECTED_STATUS,
+ CANCELLED_STATUS,
+ DROPPED_STATUS,
+ SUBMITTED_STATUS,
+ APPROVED_STATUS,
+} from '../../../helpers/constants/transactions'
+
+
+const ICON_MAP = {
+ [TRANSACTION_CATEGORY_APPROVAL]: Approve,
+ [TRANSACTION_CATEGORY_INTERACTION]: Interaction,
+ [TRANSACTION_CATEGORY_SEND]: Send,
+ [TRANSACTION_CATEGORY_SIGNATURE_REQUEST]: Sign,
+ [TRANSACTION_CATEGORY_RECEIVE]: Receive,
+}
+
+const FAIL_COLOR = '#D73A49'
+const PENDING_COLOR = '#6A737D'
+const OK_COLOR = '#2F80ED'
+
+const COLOR_MAP = {
+ [SUBMITTED_STATUS]: PENDING_COLOR,
+ [UNAPPROVED_STATUS]: PENDING_COLOR,
+ [APPROVED_STATUS]: PENDING_COLOR,
+ [FAILED_STATUS]: FAIL_COLOR,
+ [REJECTED_STATUS]: FAIL_COLOR,
+ [CANCELLED_STATUS]: FAIL_COLOR,
+ [DROPPED_STATUS]: FAIL_COLOR,
+}
+
+export default function TransactionIcon ({ status, category }) {
+
+ const color = COLOR_MAP[status] || OK_COLOR
+
+ const Icon = ICON_MAP[category]
+
+ return
+}
+
+TransactionIcon.propTypes = {
+ status: PropTypes.string.isRequired,
+ category: PropTypes.string.isRequired,
+}
diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss
index 3f7af9586..330f731ec 100644
--- a/ui/app/components/app/transaction-list-item/index.scss
+++ b/ui/app/components/app/transaction-list-item/index.scss
@@ -15,29 +15,14 @@
color: $Grey-500;
}
- &--pending {
+ &--unconfirmed {
color: $Grey-500;
}
- &--pending &__primary-currency {
+ &--unconfirmed &__primary-currency {
color: $Grey-500;
}
- &__status {
- &--unapproved {
- color: $flamingo;
- }
- &--failed {
- color: $valencia;
- }
- &--cancelled {
- color: $valencia;
- }
- &--queued {
- color: $Grey-500;
- }
- }
-
&__pending-actions {
padding-top: 12px;
display: flex;
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
index bbeb777e8..b42d8c7b6 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
@@ -3,11 +3,7 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'
import ListItem from '../../ui/list-item'
import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData'
-import Approve from '../../ui/icon/approve-icon.component'
-import Interaction from '../../ui/icon/interaction-icon.component'
-import Receive from '../../ui/icon/receive-icon.component'
import Preloader from '../../ui/icon/preloader'
-import Send from '../../ui/icon/send-icon.component'
import { useI18nContext } from '../../../hooks/useI18nContext'
import { useCancelTransaction } from '../../../hooks/useCancelTransaction'
import { useRetryTransaction } from '../../../hooks/useRetryTransaction'
@@ -17,17 +13,15 @@ import TransactionListItemDetails from '../transaction-list-item-details'
import { useHistory } from 'react-router-dom'
import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes'
import {
- TRANSACTION_CATEGORY_APPROVAL,
TRANSACTION_CATEGORY_SIGNATURE_REQUEST,
- TRANSACTION_CATEGORY_INTERACTION,
- TRANSACTION_CATEGORY_SEND,
- TRANSACTION_CATEGORY_RECEIVE,
UNAPPROVED_STATUS,
FAILED_STATUS,
- CANCELLED_STATUS,
+ DROPPED_STATUS,
+ REJECTED_STATUS,
} from '../../../helpers/constants/transactions'
import { useShouldShowSpeedUp } from '../../../hooks/useShouldShowSpeedUp'
-import Sign from '../../ui/icon/sign-icon.component'
+import TransactionStatus from '../transaction-status/transaction-status.component'
+import TransactionIcon from '../transaction-icon'
export default function TransactionListItem ({ transactionGroup, isEarliestNonce = false }) {
@@ -36,7 +30,7 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce
const { hasCancelled } = transactionGroup
const [showDetails, setShowDetails] = useState(false)
- const { initialTransaction: { id } } = transactionGroup
+ const { initialTransaction: { id }, primaryTransaction } = transactionGroup
const [cancelEnabled, cancelTransaction] = useCancelTransaction(transactionGroup)
const retryTransaction = useRetryTransaction(transactionGroup)
@@ -55,50 +49,12 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce
senderAddress,
} = useTransactionDisplayData(transactionGroup)
- const isApprove = category === TRANSACTION_CATEGORY_APPROVAL
const isSignatureReq = category === TRANSACTION_CATEGORY_SIGNATURE_REQUEST
- const isInteraction = category === TRANSACTION_CATEGORY_INTERACTION
- const isSend = category === TRANSACTION_CATEGORY_SEND
- const isReceive = category === TRANSACTION_CATEGORY_RECEIVE
const isUnapproved = status === UNAPPROVED_STATUS
- const isFailed = status === FAILED_STATUS
- const isCancelled = status === CANCELLED_STATUS
- const color = isFailed ? '#D73A49' : '#2F80ED'
-
- let Icon
- if (isApprove) {
- Icon = Approve
- } else if (isSend) {
- Icon = Send
- } else if (isReceive) {
- Icon = Receive
- } else if (isInteraction) {
- Icon = Interaction
- } else if (isSignatureReq) {
- Icon = Sign
- }
-
- let subtitleStatus = {date} ·
- if (isUnapproved) {
- subtitleStatus = (
- {t('unapproved')} ·
- )
- } else if (isFailed) {
- subtitleStatus = (
- {t('failed')} ·
- )
- } else if (isCancelled) {
- subtitleStatus = (
- {t('cancelled')} ·
- )
- } else if (isPending && !isEarliestNonce) {
- subtitleStatus = (
- {t('queued')} ·
- )
- }
-
- const className = classnames('transaction-list-item', { 'transaction-list-item--pending': isPending })
+ const className = classnames('transaction-list-item', {
+ 'transaction-list-item--unconfirmed': isPending || [FAILED_STATUS, DROPPED_STATUS, REJECTED_STATUS].includes(status),
+ })
const toggleShowDetails = useCallback(() => {
if (isUnapproved) {
@@ -161,9 +117,17 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce
color="#D73A49"
/>
)}
- icon={}
+ icon={}
subtitle={subtitle}
- subtitleStatus={subtitleStatus}
+ subtitleStatus={(
+
+ )}
rightContent={!isSignatureReq && (
<>
{primaryCurrency}
@@ -184,7 +148,7 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce
senderAddress={senderAddress}
recipientAddress={recipientAddress}
onRetry={retryTransaction}
- showRetry={isFailed}
+ showRetry={status === FAILED_STATUS}
showSpeedUp={shouldShowSpeedUp}
isEarliestNonce={isEarliestNonce}
onCancel={cancelTransaction}
diff --git a/ui/app/components/app/transaction-status/index.scss b/ui/app/components/app/transaction-status/index.scss
index 99884d28c..13dcd6c30 100644
--- a/ui/app/components/app/transaction-status/index.scss
+++ b/ui/app/components/app/transaction-status/index.scss
@@ -1,52 +1,24 @@
.transaction-status {
- height: 26px;
- width: 84px;
- border-radius: 4px;
- background-color: #f0f0f0;
- color: #5e6064;
- font-size: .625rem;
- text-transform: uppercase;
- display: flex;
- justify-content: center;
- align-items: center;
-
- @media screen and (max-width: $break-small) {
- height: 16px;
- min-width: 72px;
- font-size: 10px;
- padding: 0 12px;
+ display: inline;
+ &--unapproved {
+ color: $Orange-500;
}
-
- &--confirmed {
- background-color: #eafad7;
- color: #609a1c;
-
- .transaction-status__transaction-count {
- border: 1px solid #609a1c;
- }
+ &--failed {
+ color: $Red-500;
}
-
- &--approved, &--submitted {
- background-color: #FFF2DB;
- color: #CA810A;
-
- .transaction-status__transaction-count {
- border: 1px solid #CA810A;
- }
+ &--cancelled {
+ color: $Red-500;
}
-
- &--failed {
- background: lighten($monzo, 56%);
- color: $monzo;
-
- .transaction-status__transaction-count {
- border: 1px solid $monzo;
- }
+ &--dropped {
+ color: $Red-500;
+ }
+ &--rejected {
+ color: $Red-500;
+ }
+ &--pending {
+ color: $Orange-500;
}
-
- &__pending-spinner {
- height: 16px;
- width: 16px;
- margin-right: 6px;
+ &--queued {
+ color: $Grey-500;
}
}
diff --git a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js
index 510950248..939090875 100644
--- a/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js
+++ b/ui/app/components/app/transaction-status/tests/transaction-status.component.test.js
@@ -1,33 +1,80 @@
import React from 'react'
import assert from 'assert'
import { mount } from 'enzyme'
+import sinon from 'sinon'
+import * as i18nHook from '../../../../hooks/useI18nContext'
import TransactionStatus from '../transaction-status.component'
import Tooltip from '../../../ui/tooltip-v2'
describe('TransactionStatus Component', function () {
- it('should render APPROVED properly', function () {
+ before(function () {
+ sinon.stub(i18nHook, 'useI18nContext').returns((str) => str.toUpperCase())
+ })
+
+ it('should render CONFIRMED properly', function () {
const wrapper = mount(
,
- { context: { t: (str) => str.toUpperCase() } }
+ status="confirmed"
+ date="June 1"
+ />
)
assert.ok(wrapper)
- assert.equal(wrapper.text(), 'APPROVED')
+ assert.equal(wrapper.text(), 'June 1 · ')
+ })
+
+ it('should render PENDING properly when status is APPROVED', function () {
+ const wrapper = mount(
+
+ )
+
+ assert.ok(wrapper)
+ assert.equal(wrapper.text(), 'PENDING · ')
assert.equal(wrapper.find(Tooltip).props().title, 'test-title')
})
- it('should render SUBMITTED properly', function () {
+ it('should render PENDING properly', function () {
const wrapper = mount(
,
- { context: { t: (str) => str.toUpperCase() } }
+ date="June 1"
+ status="submitted"
+ isEarliestNonce
+ />
)
assert.ok(wrapper)
- assert.equal(wrapper.text(), 'PENDING')
+ assert.equal(wrapper.text(), 'PENDING · ')
+ })
+
+ it('should render QUEUED properly', function () {
+ const wrapper = mount(
+
+ )
+
+ assert.ok(wrapper)
+ assert.ok(wrapper.find('.transaction-status--queued').length, 'queued className not found')
+ assert.equal(wrapper.text(), 'QUEUED · ')
+ })
+
+ it('should render UNAPPROVED properly', function () {
+ const wrapper = mount(
+
+ )
+
+ assert.ok(wrapper)
+ assert.ok(wrapper.find('.transaction-status--unapproved').length, 'unapproved className not found')
+ assert.equal(wrapper.text(), 'UNAPPROVED · ')
+ })
+
+ after(function () {
+ sinon.restore()
})
})
diff --git a/ui/app/components/app/transaction-status/transaction-status.component.js b/ui/app/components/app/transaction-status/transaction-status.component.js
index a97b79bde..c525f7484 100644
--- a/ui/app/components/app/transaction-status/transaction-status.component.js
+++ b/ui/app/components/app/transaction-status/transaction-status.component.js
@@ -1,66 +1,78 @@
-import React, { PureComponent } from 'react'
+import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Tooltip from '../../ui/tooltip-v2'
-import Spinner from '../../ui/spinner'
import {
UNAPPROVED_STATUS,
REJECTED_STATUS,
- APPROVED_STATUS,
- SIGNED_STATUS,
SUBMITTED_STATUS,
CONFIRMED_STATUS,
FAILED_STATUS,
DROPPED_STATUS,
CANCELLED_STATUS,
+ APPROVED_STATUS,
+ SIGNED_STATUS,
} from '../../../helpers/constants/transactions'
+import { useI18nContext } from '../../../hooks/useI18nContext'
+
+const QUEUED_PSEUDO_STATUS = 'queued'
+const PENDING_PSEUDO_STATUS = 'pending'
+
+/**
+ * A note about status logic for this component:
+ * Approved, Signed and Submitted statuses are all treated, effectively
+ * as pending. Transactions are only approved or signed for less than a
+ * second, usually, and ultimately should be rendered in the UI no
+ * differently than a pending transaction.
+ *
+ * Confirmed transactions are not especially highlighted except that their
+ * status label will be the date the transaction was finalized.
+ */
+const pendingStatusHash = {
+ [SUBMITTED_STATUS]: PENDING_PSEUDO_STATUS,
+ [APPROVED_STATUS]: PENDING_PSEUDO_STATUS,
+ [SIGNED_STATUS]: PENDING_PSEUDO_STATUS,
+}
const statusToClassNameHash = {
[UNAPPROVED_STATUS]: 'transaction-status--unapproved',
[REJECTED_STATUS]: 'transaction-status--rejected',
- [APPROVED_STATUS]: 'transaction-status--approved',
- [SIGNED_STATUS]: 'transaction-status--signed',
- [SUBMITTED_STATUS]: 'transaction-status--submitted',
- [CONFIRMED_STATUS]: 'transaction-status--confirmed',
[FAILED_STATUS]: 'transaction-status--failed',
[DROPPED_STATUS]: 'transaction-status--dropped',
- [CANCELLED_STATUS]: 'transaction-status--failed',
+ [CANCELLED_STATUS]: 'transaction-status--cancelled',
+ [QUEUED_PSEUDO_STATUS]: 'transaction-status--queued',
+ [PENDING_PSEUDO_STATUS]: 'transaction-status--pending',
}
-const statusToTextHash = {
- [SUBMITTED_STATUS]: 'pending',
-}
-
-export default class TransactionStatus extends PureComponent {
- static defaultProps = {
- title: null,
+export default function TransactionStatus ({ status, date, error, isEarliestNonce, className }) {
+ const t = useI18nContext()
+ const tooltipText = error?.rpc?.message || error?.message
+ let statusKey = status
+ if (pendingStatusHash[status]) {
+ statusKey = isEarliestNonce ? PENDING_PSEUDO_STATUS : QUEUED_PSEUDO_STATUS
}
- static contextTypes = {
- t: PropTypes.func,
- }
+ const statusText = statusKey === CONFIRMED_STATUS ? date : t(statusKey)
- static propTypes = {
- statusKey: PropTypes.string,
- className: PropTypes.string,
- title: PropTypes.string,
- }
-
- render () {
- const { className, statusKey, title } = this.props
- const statusText = this.context.t(statusToTextHash[statusKey] || statusKey)
+ return (
+
+
+ { statusText }
+
+ {' · '}
+
+ )
+}
- return (
-
- { statusToTextHash[statusKey] === 'pending' ? : null }
-
- { statusText }
-
-
- )
- }
+TransactionStatus.propTypes = {
+ status: PropTypes.string,
+ className: PropTypes.string,
+ date: PropTypes.string,
+ error: PropTypes.object,
+ isEarliestNonce: PropTypes.bool,
}