From 8abc5144135b40a547c791e09817721547a1afea Mon Sep 17 00:00:00 2001 From: apanizo Date: Sat, 26 May 2018 09:56:17 +0200 Subject: [PATCH] WA-238 Transaction List component --- .../component/AddTransaction/transactions.js | 4 +- src/routes/safe/component/Safe/index.jsx | 5 +- .../Transactions/Collapsed/Confirmations.jsx | 79 +++++++++++++++++++ .../Transactions/Collapsed/index.jsx | 56 +++++++++++++ .../Transactions/NoTransactions/index.jsx | 32 ++++++++ .../Transactions/Transaction/index.jsx | 67 ++++++++++++++++ .../Transactions/Transaction/selector.js | 11 +++ .../safe/component/Transactions/index.jsx | 40 ++++++++++ .../safe/component/Transactions/selector.js | 13 +++ src/routes/safe/store/selectors/index.js | 51 +++++++++++- 10 files changed, 353 insertions(+), 5 deletions(-) create mode 100644 src/routes/safe/component/Transactions/Collapsed/Confirmations.jsx create mode 100644 src/routes/safe/component/Transactions/Collapsed/index.jsx create mode 100644 src/routes/safe/component/Transactions/NoTransactions/index.jsx create mode 100644 src/routes/safe/component/Transactions/Transaction/index.jsx create mode 100644 src/routes/safe/component/Transactions/Transaction/selector.js create mode 100644 src/routes/safe/component/Transactions/index.jsx create mode 100644 src/routes/safe/component/Transactions/selector.js diff --git a/src/routes/safe/component/AddTransaction/transactions.js b/src/routes/safe/component/AddTransaction/transactions.js index 468d66b8..e5ee1544 100644 --- a/src/routes/safe/component/AddTransaction/transactions.js +++ b/src/routes/safe/component/AddTransaction/transactions.js @@ -94,9 +94,9 @@ export const createTransaction = async ( const CALL = 0 if (hasOneOwner(safe)) { - const txHash = await gnosisSafe.execTransactionIfApproved(txDestination, valueInWei, '0x', CALL, nonce, { from: user, gas: '5000000' }) + const txReceipt = await gnosisSafe.execTransactionIfApproved(txDestination, valueInWei, '0x', CALL, nonce, { from: user, gas: '5000000' }) const executedConfirmations: List = buildExecutedConfirmationFrom(safe.get('owners'), user) - return storeTransaction(txName, nonce, txDestination, txValue, user, executedConfirmations, txHash.tx, safeAddress, safe.get('confirmations')) + return storeTransaction(txName, nonce, txDestination, txValue, user, executedConfirmations, txReceipt.tx, safeAddress, safe.get('confirmations')) } const txConfirmationHash = await gnosisSafe.approveTransactionWithParameters(txDestination, valueInWei, '0x', CALL, nonce, { from: user, gas: '5000000' }) diff --git a/src/routes/safe/component/Safe/index.jsx b/src/routes/safe/component/Safe/index.jsx index 36f2a5c2..6852dc1b 100644 --- a/src/routes/safe/component/Safe/index.jsx +++ b/src/routes/safe/component/Safe/index.jsx @@ -10,6 +10,7 @@ import { type Safe } from '~/routes/safe/store/model/safe' import List from 'material-ui/List' import Withdrawn from '~/routes/safe/component/Withdrawn' +import Transactions from '~/routes/safe/component/Transactions' import AddTransaction from '~/routes/safe/component/AddTransaction' import Address from './Address' @@ -55,7 +56,7 @@ class GnoSafe extends React.PureComponent { onListTransactions = () => { const { safe } = this.props - this.setState({ component: }) + this.setState({ component: }) } render() { @@ -81,7 +82,7 @@ class GnoSafe extends React.PureComponent { - + { component || Safe Icon } diff --git a/src/routes/safe/component/Transactions/Collapsed/Confirmations.jsx b/src/routes/safe/component/Transactions/Collapsed/Confirmations.jsx new file mode 100644 index 00000000..8822cffb --- /dev/null +++ b/src/routes/safe/component/Transactions/Collapsed/Confirmations.jsx @@ -0,0 +1,79 @@ +// @flow +import * as React from 'react' +import openHoc, { type Open } from '~/components/hoc/OpenHoc' +import { withStyles } from 'material-ui/styles' +import Collapse from 'material-ui/transitions/Collapse' +import ListItemText from '~/components/List/ListItemText' +import List, { ListItem, ListItemIcon } from 'material-ui/List' +import Avatar from 'material-ui/Avatar' +import Group from 'material-ui-icons/Group' +import Person from 'material-ui-icons/Person' +import ExpandLess from 'material-ui-icons/ExpandLess' +import ExpandMore from 'material-ui-icons/ExpandMore' +import { type WithStyles } from '~/theme/mui' +import { type Confirmation, type ConfirmationProps } from '~/routes/safe/store/model/confirmation' + +const styles = { + nested: { + paddingLeft: '40px', + }, +} + +type Props = Open & WithStyles & { + confirmations: List, +} + +const GnoConfirmation = ({ owner, status, hash }: ConfirmationProps) => { + const address = owner.get('address') + const text = status ? 'Confirmed' : 'Not confirmed' + const hashText = status ? `Confirmation hash: ${hash}` : undefined + + return ( + + + + + + + + + ) +} + +const Confirmaitons = openHoc(({ + open, toggle, confirmations, +}: Props) => { + const threshold = confirmations.count() + + return ( + + + + + + + + {open ? : } + + + + + {confirmations.map(confirmation => ( + + ))} + + + + ) +}) + +export default withStyles(styles)(Confirmaitons) diff --git a/src/routes/safe/component/Transactions/Collapsed/index.jsx b/src/routes/safe/component/Transactions/Collapsed/index.jsx new file mode 100644 index 00000000..64f3cf92 --- /dev/null +++ b/src/routes/safe/component/Transactions/Collapsed/index.jsx @@ -0,0 +1,56 @@ +// @flow +import * as React from 'react' +import { List as ImmutableList } from 'immutable' +import Row from '~/components/layout/Row' +import Col from '~/components/layout/Col' +import List, { ListItem, ListItemText } from 'material-ui/List' +import Avatar from 'material-ui/Avatar' +import Group from 'material-ui-icons/Group' +import MailOutline from 'material-ui-icons/MailOutline' +import { type Confirmation } from '~/routes/safe/store/model/confirmation' +import Confirmations from './Confirmations' + +type Props = { + safeName: string, + confirmations: ImmutableList, + destination: string, + tx: string, +} + +const listStyle = { + width: '100%', +} + +class Collapsed extends React.PureComponent { + render() { + const { + confirmations, destination, safeName, tx, + } = this.props + + return ( + + + + + + + + + + + + + { tx && + + + + + } + + + + ) + } +} + +export default Collapsed diff --git a/src/routes/safe/component/Transactions/NoTransactions/index.jsx b/src/routes/safe/component/Transactions/NoTransactions/index.jsx new file mode 100644 index 00000000..28e8e68a --- /dev/null +++ b/src/routes/safe/component/Transactions/NoTransactions/index.jsx @@ -0,0 +1,32 @@ +// @flow +import * as React from 'react' +import Bold from '~/components/layout/Bold' +import Button from '~/components/layout/Button' +import Col from '~/components/layout/Col' +import Row from '~/components/layout/Row' +import Paragraph from '~/components/layout/Paragraph/index' + +type Props = { + onAddTx: () => void +} + +const NoRights = ({ onAddTx }: Props) => ( + + + + No transactions found for this safe + + + + + + +) + +export default NoRights diff --git a/src/routes/safe/component/Transactions/Transaction/index.jsx b/src/routes/safe/component/Transactions/Transaction/index.jsx new file mode 100644 index 00000000..f36012e8 --- /dev/null +++ b/src/routes/safe/component/Transactions/Transaction/index.jsx @@ -0,0 +1,67 @@ +// @flow +import * as React from 'react' +import { connect } from 'react-redux' +import openHoc, { type Open } from '~/components/hoc/OpenHoc' +import ExpandLess from 'material-ui-icons/ExpandLess' +import ExpandMore from 'material-ui-icons/ExpandMore' +import ListItemText from '~/components/List/ListItemText' +import Row from '~/components/layout/Row' +import { ListItem, ListItemIcon } from 'material-ui/List' +import Avatar from 'material-ui/Avatar' +import AttachMoney from 'material-ui-icons/AttachMoney' +import Atm from 'material-ui-icons/LocalAtm' +import DoneAll from 'material-ui-icons/DoneAll' +import Collapsed from '~/routes/safe/component/Transactions/Collapsed' +import { type Transaction } from '~/routes/safe/store/model/transaction' +import Hairline from '~/components/layout/Hairline/index' +import selector, { type SelectorProps } from './selector' + +type Props = Open & SelectorProps & { + transaction: Transaction, + safeName: string, +} + +class GnoTransaction extends React.PureComponent { + render() { + const { + open, toggle, transaction, confirmed, safeName, + } = this.props + + const txHash = transaction.get('tx') + const confirmationText = txHash ? 'Already executed' : `${confirmed} of the ${transaction.get('threshold')} confirmations needed` + + return ( + + + + + + + + + + + + + + + + + {open ? : } + + + + { open && + } + + + ) + } +} + +export default connect(selector)(openHoc(GnoTransaction)) diff --git a/src/routes/safe/component/Transactions/Transaction/selector.js b/src/routes/safe/component/Transactions/Transaction/selector.js new file mode 100644 index 00000000..14b4f3ad --- /dev/null +++ b/src/routes/safe/component/Transactions/Transaction/selector.js @@ -0,0 +1,11 @@ +// @flow +import { createStructuredSelector } from 'reselect' +import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index' + +export type SelectorProps = { + confirmed: confirmationsTransactionSelector, +} + +export default createStructuredSelector({ + confirmed: confirmationsTransactionSelector, +}) diff --git a/src/routes/safe/component/Transactions/index.jsx b/src/routes/safe/component/Transactions/index.jsx new file mode 100644 index 00000000..11c595f0 --- /dev/null +++ b/src/routes/safe/component/Transactions/index.jsx @@ -0,0 +1,40 @@ +// @flow +import * as React from 'react' +import { connect } from 'react-redux' +import { type Transaction } from '~/routes/safe/store/model/transaction' +import NoTransactions from '~/routes/safe/component/Transactions/NoTransactions' +import GnoTransaction from '~/routes/safe/component/Transactions/Transaction' +import selector, { type SelectorProps } from './selector' + +type Props = SelectorProps & { + onAddTx: () => void, + safeName: string, +} + +class Transactions extends React.Component { + onConfirm = () => { + // eslint-disable-next-line + console.log("Confirming tx") + } + + onExecute = () => { + // eslint-disable-next-line + console.log("Confirming tx") + } + + render() { + const { transactions, onAddTx, safeName } = this.props + const hasTransactions = transactions.count() > 0 + + return ( + + { hasTransactions + ? transactions.map((tx: Transaction) => ) + : + } + + ) + } +} + +export default connect(selector)(Transactions) diff --git a/src/routes/safe/component/Transactions/selector.js b/src/routes/safe/component/Transactions/selector.js new file mode 100644 index 00000000..1941f7c5 --- /dev/null +++ b/src/routes/safe/component/Transactions/selector.js @@ -0,0 +1,13 @@ +// @flow +import { List } from 'immutable' +import { createStructuredSelector } from 'reselect' +import { type Transaction } from '~/routes/safe/store/model/transaction' +import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index' + +export type SelectorProps = { + transactions: List, +} + +export default createStructuredSelector({ + transactions: safeTransactionsSelector, +}) diff --git a/src/routes/safe/store/selectors/index.js b/src/routes/safe/store/selectors/index.js index e6bf40eb..f89a7b8b 100644 --- a/src/routes/safe/store/selectors/index.js +++ b/src/routes/safe/store/selectors/index.js @@ -1,5 +1,5 @@ // @flow -import { Map } from 'immutable' +import { Map, List } from 'immutable' import { type Match } from 'react-router-dom' import { createSelector, createStructuredSelector, type Selector } from 'reselect' import { type GlobalState } from '~/store/index' @@ -7,15 +7,64 @@ import { SAFE_PARAM_ADDRESS } from '~/routes/routes' import { type Safe } from '~/routes/safe/store/model/safe' import { safesMapSelector } from '~/routes/safeList/store/selectors' import { BALANCE_REDUCER_ID } from '~/routes/safe/store/reducer/balances' +import { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions' +import { type Transaction } from '~/routes/safe/store/model/transaction' +import { type Confirmation } from '~/routes/safe/store/model/confirmation' export type RouterProps = { match: Match, } +export type SafeProps = { + safeAddress: string, +} + +type TransactionProps = { + transaction: Transaction, +} + +const safeAddressSelector = (state: GlobalState, props: SafeProps) => props.safeAddress + const safeAddessSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || '' const balancesSelector = (state: GlobalState) => state[BALANCE_REDUCER_ID] +const transactionsSelector = (state: GlobalState): TransactionsState => state[TRANSACTIONS_REDUCER_ID] + +const oneTransactionSelector = (state: GlobalState, props: TransactionProps) => props.transaction + +export const safeTransactionsSelector: Selector> = createSelector( + transactionsSelector, + safeAddressSelector, + (transactions: TransactionsState, address: string): List => { + if (!transactions) { + return List([]) + } + + if (!address) { + return List([]) + } + + return transactions.get(address) || List([]) + }, +) + +export const confirmationsTransactionSelector = createSelector( + oneTransactionSelector, + (tx: Transaction) => { + if (!tx) { + return 0 + } + + const confirmations: List = tx.get('confirmations') + if (!confirmations) { + return 0 + } + + return confirmations.filter(((confirmation: Confirmation) => confirmation.get('status'))).count() + }, +) + export type SafeSelectorProps = Safe | typeof undefined export const safeSelector: Selector = createSelector(