WA-238 Transaction List component
This commit is contained in:
parent
48a5804051
commit
8abc514413
|
@ -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<Confirmation> = 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' })
|
||||
|
|
|
@ -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<SafeProps, State> {
|
|||
onListTransactions = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
this.setState({ component: <Withdrawn safeAddress={safe.get('address')} dailyLimit={safe.get('dailyLimit')} /> })
|
||||
this.setState({ component: <Transactions safeName={safe.get('name')} safeAddress={safe.get('address')} onAddTx={this.onAddTx} /> })
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -81,7 +82,7 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
|
|||
</Paragraph>
|
||||
</Block>
|
||||
<Row grow>
|
||||
<Col sm={12} center="sm" middle={component ? undefined : 'sm'} layout="column">
|
||||
<Col sm={12} center={component ? undefined : 'sm'} middle={component ? undefined : 'sm'} layout="column">
|
||||
{ component || <Img alt="Safe Icon" src={safeIcon} height={330} /> }
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -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<Confirmation>,
|
||||
}
|
||||
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<ListItem key={address}>
|
||||
<ListItemIcon>
|
||||
<Person />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
cut
|
||||
primary={`${owner.get('name')} [${text}]`}
|
||||
secondary={hashText}
|
||||
/>
|
||||
</ListItem>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const Confirmaitons = openHoc(({
|
||||
open, toggle, confirmations,
|
||||
}: Props) => {
|
||||
const threshold = confirmations.count()
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListItem onClick={toggle}>
|
||||
<Avatar>
|
||||
<Group />
|
||||
</Avatar>
|
||||
<ListItemText primary="Threshold" secondary={`${threshold} confirmation${threshold === 1 ? '' : 's'} needed`} />
|
||||
<ListItemIcon>
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding style={{ width: '100%' }}>
|
||||
{confirmations.map(confirmation => (
|
||||
<GnoConfirmation
|
||||
key={confirmation.get('owner').get('address')}
|
||||
owner={confirmation.get('owner')}
|
||||
status={confirmation.get('status')}
|
||||
hash={confirmation.get('hash')}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
|
||||
export default withStyles(styles)(Confirmaitons)
|
|
@ -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<Confirmation>,
|
||||
destination: string,
|
||||
tx: string,
|
||||
}
|
||||
|
||||
const listStyle = {
|
||||
width: '100%',
|
||||
}
|
||||
|
||||
class Collapsed extends React.PureComponent<Props, {}> {
|
||||
render() {
|
||||
const {
|
||||
confirmations, destination, safeName, tx,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col sm={12} top="xs" overflow>
|
||||
<List style={listStyle}>
|
||||
<ListItem>
|
||||
<Avatar><Group /></Avatar>
|
||||
<ListItemText primary={safeName} secondary="Safe Name" />
|
||||
</ListItem>
|
||||
<Confirmations confirmations={confirmations} />
|
||||
<ListItem>
|
||||
<Avatar><MailOutline /></Avatar>
|
||||
<ListItemText primary="Destination" secondary={destination} />
|
||||
</ListItem>
|
||||
{ tx &&
|
||||
<ListItem>
|
||||
<Avatar><MailOutline /></Avatar>
|
||||
<ListItemText cut primary="Transaction Hash" secondary={tx} />
|
||||
</ListItem>
|
||||
}
|
||||
</List>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Collapsed
|
|
@ -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) => (
|
||||
<Row>
|
||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||
<Paragraph size="lg">
|
||||
<Bold>No transactions found for this safe</Bold>
|
||||
</Paragraph>
|
||||
</Col>
|
||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||
<Button
|
||||
onClick={onAddTx}
|
||||
variant="raised"
|
||||
color="primary"
|
||||
>
|
||||
Add Multisig Transaction
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
|
||||
export default NoRights
|
|
@ -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<Props, {}> {
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<Row>
|
||||
<ListItem onClick={toggle}>
|
||||
<Avatar>
|
||||
<Atm />
|
||||
</Avatar>
|
||||
<ListItemText primary="Tx Name" secondary={transaction.get('name')} />
|
||||
<Avatar>
|
||||
<AttachMoney />
|
||||
</Avatar>
|
||||
<ListItemText primary="Value" secondary={`${transaction.get('value')} ETH`} />
|
||||
<Avatar>
|
||||
<DoneAll />
|
||||
</Avatar>
|
||||
<ListItemText primary="Status" secondary={confirmationText} />
|
||||
<ListItemIcon>
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
</Row>
|
||||
{ open &&
|
||||
<Collapsed
|
||||
safeName={safeName}
|
||||
confirmations={transaction.get('confirmations')}
|
||||
destination={transaction.get('destination')}
|
||||
tx={transaction.get('tx')}
|
||||
/> }
|
||||
<Hairline />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector)(openHoc(GnoTransaction))
|
|
@ -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,
|
||||
})
|
|
@ -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<Props, {}> {
|
||||
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 (
|
||||
<React.Fragment>
|
||||
{ hasTransactions
|
||||
? transactions.map((tx: Transaction) => <GnoTransaction key={tx.get('nonce')} safeName={safeName} transaction={tx} />)
|
||||
: <NoTransactions onAddTx={onAddTx} />
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector)(Transactions)
|
|
@ -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<Transaction>,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
transactions: safeTransactionsSelector,
|
||||
})
|
|
@ -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<GlobalState, SafeProps, List<Transaction>> = createSelector(
|
||||
transactionsSelector,
|
||||
safeAddressSelector,
|
||||
(transactions: TransactionsState, address: string): List<Transaction> => {
|
||||
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<Confirmation> = 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<GlobalState, RouterProps, SafeSelectorProps> = createSelector(
|
||||
|
|
Loading…
Reference in New Issue