From be61d804e0af642a9363c763d138a453a68c1026 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 16 Feb 2018 11:57:23 -0500 Subject: [PATCH] Check Transaction page (Pt. 1 - The Basics) (#1099) * Component layer and routing for transaction status. * Initial start on redux for transactions. * Initial crack at reducer / actions / saga for transactions. * Finish off check transaction saga, reducer, component, and page. --- common/Root.tsx | 5 +- common/actions/transactions/actionCreators.ts | 20 ++ common/actions/transactions/actionTypes.ts | 20 ++ common/actions/transactions/constants.ts | 5 + common/actions/transactions/index.ts | 3 + .../Header/components/Navigation.tsx | 4 + .../TransactionDataTable.scss | 49 +++++ .../TransactionDataTable.tsx | 174 ++++++++++++++++++ .../TransactionStatus/TransactionStatus.scss | 35 ++++ .../TransactionStatus/TransactionStatus.tsx | 88 +++++++++ common/components/TransactionStatus/index.tsx | 2 + common/components/index.ts | 1 + common/components/ui/NewTabLink.tsx | 4 +- .../components/TxHashInput.scss | 4 + .../components/TxHashInput.tsx | 63 +++++++ .../Tabs/CheckTransaction/index.scss | 12 ++ .../Tabs/CheckTransaction/index.tsx | 69 +++++++ common/libs/nodes/INode.ts | 31 ++++ common/libs/nodes/etherscan/requests.ts | 9 + common/libs/nodes/etherscan/types.ts | 7 + common/libs/nodes/index.ts | 1 + common/libs/nodes/rpc/index.ts | 40 +++- common/libs/nodes/rpc/requests.ts | 20 +- common/libs/nodes/rpc/types.ts | 12 ++ common/libs/validators.ts | 13 +- .../config/networks/staticNetworks.ts | 20 +- common/reducers/index.ts | 12 +- common/reducers/transactions.ts | 63 +++++++ common/sagas/index.ts | 4 +- common/sagas/transactions.ts | 39 ++++ common/selectors/transactions.ts | 5 + common/translations/lang/en.json | 4 - common/utils/formatters.ts | 6 + shared/types/network.d.ts | 4 +- .../config/networks/staticNetworks.spec.ts | 14 +- 35 files changed, 825 insertions(+), 37 deletions(-) create mode 100644 common/actions/transactions/actionCreators.ts create mode 100644 common/actions/transactions/actionTypes.ts create mode 100644 common/actions/transactions/constants.ts create mode 100644 common/actions/transactions/index.ts create mode 100644 common/components/TransactionStatus/TransactionDataTable.scss create mode 100644 common/components/TransactionStatus/TransactionDataTable.tsx create mode 100644 common/components/TransactionStatus/TransactionStatus.scss create mode 100644 common/components/TransactionStatus/TransactionStatus.tsx create mode 100644 common/components/TransactionStatus/index.tsx create mode 100644 common/containers/Tabs/CheckTransaction/components/TxHashInput.scss create mode 100644 common/containers/Tabs/CheckTransaction/components/TxHashInput.tsx create mode 100644 common/containers/Tabs/CheckTransaction/index.scss create mode 100644 common/containers/Tabs/CheckTransaction/index.tsx create mode 100644 common/reducers/transactions.ts create mode 100644 common/sagas/transactions.ts create mode 100644 common/selectors/transactions.ts diff --git a/common/Root.tsx b/common/Root.tsx index 0de08d6f..572cae7d 100644 --- a/common/Root.tsx +++ b/common/Root.tsx @@ -9,6 +9,7 @@ import SendTransaction from 'containers/Tabs/SendTransaction'; import Swap from 'containers/Tabs/Swap'; import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage'; import BroadcastTx from 'containers/Tabs/BroadcastTx'; +import CheckTransaction from 'containers/Tabs/CheckTransaction'; import ErrorScreen from 'components/ErrorScreen'; import PageNotFound from 'components/PageNotFound'; import LogOutPrompt from 'components/LogOutPrompt'; @@ -67,6 +68,7 @@ export default class Root extends Component { + @@ -120,8 +122,7 @@ const LegacyRoutes = withRouter(props => { history.push('/account/info'); break; case '#check-tx-status': - history.push('/check-tx-status'); - break; + return ; } } diff --git a/common/actions/transactions/actionCreators.ts b/common/actions/transactions/actionCreators.ts new file mode 100644 index 00000000..30e2ff95 --- /dev/null +++ b/common/actions/transactions/actionCreators.ts @@ -0,0 +1,20 @@ +import * as interfaces from './actionTypes'; +import { TypeKeys } from './constants'; + +export type TFetchTransactionData = typeof fetchTransactionData; +export function fetchTransactionData(txhash: string): interfaces.FetchTransactionDataAction { + return { + type: TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA, + payload: txhash + }; +} + +export type TSetTransactionData = typeof setTransactionData; +export function setTransactionData( + payload: interfaces.SetTransactionDataAction['payload'] +): interfaces.SetTransactionDataAction { + return { + type: TypeKeys.TRANSACTIONS_SET_TRANSACTION_DATA, + payload + }; +} diff --git a/common/actions/transactions/actionTypes.ts b/common/actions/transactions/actionTypes.ts new file mode 100644 index 00000000..ee4380b3 --- /dev/null +++ b/common/actions/transactions/actionTypes.ts @@ -0,0 +1,20 @@ +import { TypeKeys } from './constants'; +import { TransactionData, TransactionReceipt } from 'libs/nodes'; + +export interface FetchTransactionDataAction { + type: TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA; + payload: string; +} + +export interface SetTransactionDataAction { + type: TypeKeys.TRANSACTIONS_SET_TRANSACTION_DATA; + payload: { + txhash: string; + data: TransactionData | null; + receipt: TransactionReceipt | null; + error: string | null; + }; +} + +/*** Union Type ***/ +export type TransactionsAction = FetchTransactionDataAction | SetTransactionDataAction; diff --git a/common/actions/transactions/constants.ts b/common/actions/transactions/constants.ts new file mode 100644 index 00000000..cbd8ad82 --- /dev/null +++ b/common/actions/transactions/constants.ts @@ -0,0 +1,5 @@ +export enum TypeKeys { + TRANSACTIONS_FETCH_TRANSACTION_DATA = 'TRANSACTIONS_FETCH_TRANSACTION_DATA', + TRANSACTIONS_SET_TRANSACTION_DATA = 'TRANSACTIONS_SET_TRANSACTION_DATA', + TRANSACTIONS_SET_TRANSACTION_ERROR = 'TRANSACTIONS_SET_TRANSACTION_ERROR' +} diff --git a/common/actions/transactions/index.ts b/common/actions/transactions/index.ts new file mode 100644 index 00000000..51fcd517 --- /dev/null +++ b/common/actions/transactions/index.ts @@ -0,0 +1,3 @@ +export * from './actionCreators'; +export * from './actionTypes'; +export * from './constants'; diff --git a/common/components/Header/components/Navigation.tsx b/common/components/Header/components/Navigation.tsx index 2695082f..35321754 100644 --- a/common/components/Header/components/Navigation.tsx +++ b/common/components/Header/components/Navigation.tsx @@ -34,6 +34,10 @@ const tabs: TabLink[] = [ name: 'Sign & Verify Message', to: '/sign-and-verify-message' }, + { + name: 'NAV_TxStatus', + to: '/tx-status' + }, { name: 'Broadcast Transaction', to: '/pushTx' diff --git a/common/components/TransactionStatus/TransactionDataTable.scss b/common/components/TransactionStatus/TransactionDataTable.scss new file mode 100644 index 00000000..be38bb82 --- /dev/null +++ b/common/components/TransactionStatus/TransactionDataTable.scss @@ -0,0 +1,49 @@ +@import 'common/sass/variables'; +@import 'common/sass/mixins'; + +.TxData { + &-row { + font-size: 0.9rem; + + &-label { + font-weight: bold; + text-align: right; + } + + &-data { + @include mono; + + &-more { + margin-left: $space-sm; + font-size: 0.7rem; + opacity: 0.7; + } + + &-status { + font-weight: bold; + + &.is-success { + color: $brand-success; + } + &.is-warning { + color: $brand-warning; + } + &.is-danger { + color: $brand-danger; + } + } + + .Identicon { + float: left; + margin-right: $space-sm; + } + + textarea { + display: block; + width: 100%; + height: 7.2rem; + background: $gray-lighter; + } + } + } +} diff --git a/common/components/TransactionStatus/TransactionDataTable.tsx b/common/components/TransactionStatus/TransactionDataTable.tsx new file mode 100644 index 00000000..4e5d2a6c --- /dev/null +++ b/common/components/TransactionStatus/TransactionDataTable.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import translate from 'translations'; +import { Identicon, UnitDisplay, NewTabLink } from 'components/ui'; +import { TransactionData, TransactionReceipt } from 'libs/nodes'; +import { NetworkConfig } from 'types/network'; +import './TransactionDataTable.scss'; + +interface TableRow { + label: React.ReactElement | string; + data: React.ReactElement | string | number | null; +} + +const MaybeLink: React.SFC<{ + href: string | null | undefined | false; + children: any; // Too many damn React element types +}> = ({ href, children }) => { + if (href) { + return {children}; + } else { + return {children}; + } +}; + +interface Props { + data: TransactionData; + receipt: TransactionReceipt | null; + network: NetworkConfig; +} + +const TransactionDataTable: React.SFC = ({ data, receipt, network }) => { + const explorer: { [key: string]: string | false | null } = {}; + const hasInputData = data.input && data.input !== '0x'; + + if (!network.isCustom) { + explorer.tx = network.blockExplorer && network.blockExplorer.txUrl(data.hash); + explorer.block = + network.blockExplorer && + !!data.blockNumber && + network.blockExplorer.blockUrl(data.blockNumber); + explorer.to = network.blockExplorer && network.blockExplorer.addressUrl(data.to); + explorer.from = network.blockExplorer && network.blockExplorer.addressUrl(data.from); + explorer.contract = + network.blockExplorer && + receipt && + receipt.contractAddress && + network.blockExplorer.addressUrl(receipt.contractAddress); + } + + let statusMsg = ''; + let statusType = ''; + let statusSeeMore = false; + if (receipt) { + if (receipt.status === 1) { + statusMsg = 'SUCCESSFUL'; + statusType = 'success'; + } else if (receipt.status === 0) { + statusMsg = 'FAILED'; + statusType = 'danger'; + statusSeeMore = true; + } else { + // Pre-byzantium transactions don't use status, and cannot have their + // success determined over the JSON RPC api + statusMsg = 'UNKNOWN'; + statusType = 'warning'; + statusSeeMore = true; + } + } else { + statusMsg = 'PENDING'; + statusType = 'warning'; + } + + const rows: TableRow[] = [ + { + label: 'Status', + data: ( + + {statusMsg} + {statusSeeMore && + explorer.tx && + !network.isCustom && ( + + (See more on {network.blockExplorer.name}) + + )} + + ) + }, + { + label: translate('x_TxHash'), + data: {data.hash} + }, + { + label: 'Block Number', + data: receipt && {receipt.blockNumber} + }, + { + label: translate('OFFLINE_Step1_Label_1'), + data: ( + + + {data.from} + + ) + }, + { + label: translate('OFFLINE_Step2_Label_1'), + data: ( + + + {data.to} + + ) + }, + { + label: translate('SEND_amount_short'), + data: + }, + { + label: translate('OFFLINE_Step2_Label_3'), + data: + }, + { + label: translate('OFFLINE_Step2_Label_4'), + data: + }, + { + label: 'Gas Used', + data: receipt && + }, + { + label: 'Transaction Fee', + data: receipt && ( + + ) + }, + { + label: translate('New contract address'), + data: receipt && + receipt.contractAddress && ( + {receipt.contractAddress} + ) + }, + { + label: translate('OFFLINE_Step2_Label_5'), + data: data.nonce + }, + { + label: translate('TRANS_data'), + data: hasInputData ? ( +