Removing Daily Limit from Safe component
This commit is contained in:
parent
471417da0b
commit
15613c869d
|
@ -1,37 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, minValue, mustBeFloat, required } from '~/components/forms/validator'
|
||||
|
||||
export const EDIT_DAILY_LIMIT_PARAM = 'daily'
|
||||
|
||||
type EditDailyLimitProps = {
|
||||
dailyLimit: string,
|
||||
}
|
||||
|
||||
const EditDailyLimitForm = ({ dailyLimit }: EditDailyLimitProps) => (controls: React$Node) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2" margin="lg">
|
||||
{'Change safe\'s daily limit'}
|
||||
</Heading>
|
||||
<Heading tag="h4" margin="lg">
|
||||
{`Actual daily limit: ${dailyLimit}`}
|
||||
</Heading>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={EDIT_DAILY_LIMIT_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeFloat, minValue(0))}
|
||||
placeholder="New daily limit"
|
||||
text="Safe's daily limit"
|
||||
/>
|
||||
</Block>
|
||||
</OpenPaper>
|
||||
)
|
||||
|
||||
export default EditDailyLimitForm
|
|
@ -1,32 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import { EDIT_DAILY_LIMIT_PARAM } from '~/routes/safe/component/EditDailyLimit/EditDailyLimitForm'
|
||||
|
||||
type FormProps = {
|
||||
values: Object,
|
||||
submitting: boolean,
|
||||
}
|
||||
|
||||
const spinnerStyle = {
|
||||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const Review = () => (controls: React$Node, { values, submitting }: FormProps) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<Heading tag="h2">Review the DailyLimit operation</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>The new daily limit will be: </Bold> {values[EDIT_DAILY_LIMIT_PARAM]}
|
||||
</Paragraph>
|
||||
<Block style={spinnerStyle}>
|
||||
{ submitting && <CircularProgress size={50} /> }
|
||||
</Block>
|
||||
</OpenPaper>
|
||||
)
|
||||
|
||||
export default Review
|
|
@ -1,12 +0,0 @@
|
|||
// @flow
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
|
||||
type FetchTransactions = typeof fetchTransactions
|
||||
|
||||
export type Actions = {
|
||||
fetchTransactions: FetchTransactions,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchTransactions,
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { connect } from 'react-redux'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { createTransaction, getSafeEthereumInstance } from '~/logic/safe/safeFrontendOperations'
|
||||
import { getEditDailyLimitData, getDailyLimitAddress } from '~/logic/contracts/dailyLimitContracts'
|
||||
import EditDailyLimitForm, { EDIT_DAILY_LIMIT_PARAM } from './EditDailyLimitForm'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
import Review from './Review'
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
dailyLimit: number,
|
||||
onReset: () => void,
|
||||
safe: Safe,
|
||||
}
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Edit Daily Limit Form', 'Review Edit Daily Limit operation',
|
||||
]
|
||||
|
||||
type State = {
|
||||
done: boolean,
|
||||
}
|
||||
|
||||
export const CHANGE_THRESHOLD_RESET_BUTTON_TEXT = 'SEE TXs'
|
||||
|
||||
class EditDailyLimit extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
done: false,
|
||||
}
|
||||
|
||||
onEditDailyLimit = async (values: Object) => {
|
||||
try {
|
||||
const { safe, userAddress } = this.props
|
||||
const newDailyLimit = values[EDIT_DAILY_LIMIT_PARAM]
|
||||
const safeAddress = safe.get('address')
|
||||
const data = await getEditDailyLimitData(safeAddress, 0, Number(newDailyLimit))
|
||||
const to = await getDailyLimitAddress(safeAddress)
|
||||
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
||||
const nonce = await gnosisSafe.nonce()
|
||||
await createTransaction(safe, `Change Safe's daily limit to ${newDailyLimit} [${nonce}]`, to, 0, nonce, userAddress, data)
|
||||
await this.props.fetchTransactions(safeAddress)
|
||||
this.setState({ done: true })
|
||||
} catch (error) {
|
||||
this.setState({ done: false })
|
||||
// eslint-disable-next-line
|
||||
console.log('Error while editing the daily limit ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
this.setState({ done: false })
|
||||
this.props.onReset()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dailyLimit } = this.props
|
||||
const { done } = this.state
|
||||
const steps = getSteps()
|
||||
const finishedButton = <Stepper.FinishButton title={CHANGE_THRESHOLD_RESET_BUTTON_TEXT} />
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stepper
|
||||
finishedTransaction={done}
|
||||
finishedButton={finishedButton}
|
||||
onSubmit={this.onEditDailyLimit}
|
||||
steps={steps}
|
||||
onReset={this.onReset}
|
||||
>
|
||||
<Stepper.Page dailyLimit={dailyLimit} >
|
||||
{ EditDailyLimitForm }
|
||||
</Stepper.Page>
|
||||
<Stepper.Page>
|
||||
{ Review }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(EditDailyLimit)
|
|
@ -1,11 +0,0 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
|
||||
export type SelectorProps = {
|
||||
userAddress: userAccountSelector,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
userAddress: userAccountSelector,
|
||||
})
|
|
@ -3,8 +3,6 @@ import { storiesOf } from '@storybook/react'
|
|||
import * as React from 'react'
|
||||
import { List } from 'immutable'
|
||||
import styles from '~/components/layout/PageFrame/index.scss'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { makeToken } from '~/routes/tokens/store/model/token'
|
||||
import Component from './Layout'
|
||||
|
||||
|
||||
|
@ -14,15 +12,6 @@ const FrameDecorator = story => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const ethBalance = makeToken({
|
||||
address: '0',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
logoUrl: 'assets/icons/icon_etherTokens.svg',
|
||||
funds: '2',
|
||||
})
|
||||
|
||||
storiesOf('Routes /safe:address', module)
|
||||
.addDecorator(FrameDecorator)
|
||||
.add('Safe undefined being connected', () => (
|
||||
|
@ -43,29 +32,3 @@ storiesOf('Routes /safe:address', module)
|
|||
fetchBalance={() => {}}
|
||||
/>
|
||||
))
|
||||
.add('Safe with 2 owners and 10ETH as dailyLimit', () => {
|
||||
const safe = SafeFactory.dailyLimitSafe(10, 1.345)
|
||||
|
||||
return (
|
||||
<Component
|
||||
userAddress="foo"
|
||||
safe={safe}
|
||||
provider="METAMASK"
|
||||
activeTokens={List([]).push(ethBalance)}
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
.add('Safe with dailyLimit reached', () => {
|
||||
const safe = SafeFactory.dailyLimitSafe(10, 10)
|
||||
|
||||
return (
|
||||
<Component
|
||||
userAddress="foo"
|
||||
safe={safe}
|
||||
provider="METAMASK"
|
||||
activeTokens={List([]).push(ethBalance)}
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import Avatar from '@material-ui/core/Avatar'
|
||||
import NotificationsPaused from '@material-ui/icons/NotificationsPaused'
|
||||
import Button from '~/components/layout/Button'
|
||||
import ListItemText from '~/components/List/ListItemText'
|
||||
import { type DailyLimit } from '~/routes/safe/store/model/dailyLimit'
|
||||
|
||||
type Props = {
|
||||
dailyLimit: DailyLimit,
|
||||
onWithdraw: () => void,
|
||||
onEditDailyLimit: () => void,
|
||||
balance: number,
|
||||
}
|
||||
export const EDIT_WITHDRAW = 'Edit'
|
||||
export const WITHDRAW_BUTTON_TEXT = 'Withdraw'
|
||||
|
||||
const editStyle = {
|
||||
marginRight: '10px',
|
||||
}
|
||||
|
||||
const DailyLimitComponent = ({
|
||||
dailyLimit, balance, onWithdraw, onEditDailyLimit,
|
||||
}: Props) => {
|
||||
const limit = dailyLimit.get('value')
|
||||
const spentToday = dailyLimit.get('spentToday')
|
||||
|
||||
const disabled = spentToday >= limit || balance === 0
|
||||
const text = `${limit} ETH (spent today: ${spentToday} ETH)`
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<Avatar>
|
||||
<NotificationsPaused />
|
||||
</Avatar>
|
||||
<ListItemText primary="Daily Limit" secondary={text} />
|
||||
<Button
|
||||
style={editStyle}
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={onEditDailyLimit}
|
||||
>
|
||||
{EDIT_WITHDRAW}
|
||||
</Button>
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={onWithdraw}
|
||||
disabled={disabled}
|
||||
>
|
||||
{WITHDRAW_BUTTON_TEXT}
|
||||
</Button>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default DailyLimitComponent
|
|
@ -11,19 +11,16 @@ import Row from '~/components/layout/Row'
|
|||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Token } from '~/routes/tokens/store/model/token'
|
||||
|
||||
import Withdraw from '~/routes/safe/component/Withdraw'
|
||||
import Transactions from '~/routes/safe/component/Transactions'
|
||||
import Threshold from '~/routes/safe/component/Threshold'
|
||||
import AddOwner from '~/routes/safe/component/AddOwner'
|
||||
import RemoveOwner from '~/routes/safe/component/RemoveOwner'
|
||||
import EditDailyLimit from '~/routes/safe/component/EditDailyLimit'
|
||||
import SendToken from '~/routes/safe/component/SendToken'
|
||||
|
||||
import Address from './Address'
|
||||
import BalanceInfo from './BalanceInfo'
|
||||
import Owners from './Owners'
|
||||
import Confirmations from './Confirmations'
|
||||
import DailyLimit from './DailyLimit'
|
||||
import MultisigTx from './MultisigTx'
|
||||
|
||||
const safeIcon = require('./assets/gnosis_safe.svg')
|
||||
|
@ -42,38 +39,11 @@ const listStyle = {
|
|||
width: '100%',
|
||||
}
|
||||
|
||||
const getEthBalanceFrom = (tokens: List<Token>) => {
|
||||
const filteredTokens = tokens.filter(token => token.get('symbol') === 'ETH')
|
||||
if (filteredTokens.count() === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const ethToken = filteredTokens.get(0)
|
||||
if (!ethToken) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Number(ethToken.get('funds'))
|
||||
}
|
||||
|
||||
class GnoSafe extends React.PureComponent<SafeProps, State> {
|
||||
state = {
|
||||
component: undefined,
|
||||
}
|
||||
|
||||
onEditDailyLimit = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
const value = safe.get('dailyLimit').get('value')
|
||||
this.setState({ component: <EditDailyLimit safe={safe} dailyLimit={value} onReset={this.onListTransactions} /> })
|
||||
}
|
||||
|
||||
onWithdraw = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
this.setState({ component: <Withdraw safe={safe} dailyLimit={safe.get('dailyLimit')} /> })
|
||||
}
|
||||
|
||||
onListTransactions = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
|
@ -114,7 +84,6 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
|
|||
render() {
|
||||
const { safe, tokens, userAddress } = this.props
|
||||
const { component } = this.state
|
||||
const ethBalance = getEthBalanceFrom(tokens)
|
||||
const address = safe.get('address')
|
||||
|
||||
return (
|
||||
|
@ -130,7 +99,6 @@ class GnoSafe extends React.PureComponent<SafeProps, State> {
|
|||
/>
|
||||
<Confirmations confirmations={safe.get('threshold')} onEditThreshold={this.onEditThreshold} />
|
||||
<Address address={address} />
|
||||
<DailyLimit balance={ethBalance} dailyLimit={safe.get('dailyLimit')} onWithdraw={this.onWithdraw} onEditDailyLimit={this.onEditDailyLimit} />
|
||||
<MultisigTx onSeeTxs={this.onListTransactions} />
|
||||
</ListComponent>
|
||||
</Col>
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import { DESTINATION_PARAM, VALUE_PARAM } from '~/routes/safe/component/Withdraw/WithdrawForm'
|
||||
|
||||
type FormProps = {
|
||||
values: Object,
|
||||
submitting: boolean,
|
||||
}
|
||||
|
||||
const spinnerStyle = {
|
||||
minHeight: '50px',
|
||||
}
|
||||
|
||||
const Review = () => ({ values, submitting }: FormProps) => (
|
||||
<Block>
|
||||
<Heading tag="h2">Review the Withdraw Operation</Heading>
|
||||
<Paragraph align="left">
|
||||
<Bold>Destination: </Bold> {values[DESTINATION_PARAM]}
|
||||
</Paragraph>
|
||||
<Paragraph align="left">
|
||||
<Bold>Value in ETH: </Bold> {values[VALUE_PARAM]}
|
||||
</Paragraph>
|
||||
<Block style={spinnerStyle}>
|
||||
{ submitting && <CircularProgress size={50} /> }
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default Review
|
|
@ -1,29 +0,0 @@
|
|||
// @flow
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import * as React from 'react'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import styles from '~/components/layout/PageFrame/index.scss'
|
||||
import WithdrawForm from './index'
|
||||
|
||||
|
||||
const FrameDecorator = story => (
|
||||
<div className={styles.frame} style={{ textAlign: 'center' }}>
|
||||
{ story() }
|
||||
</div>
|
||||
)
|
||||
|
||||
storiesOf('Components', module)
|
||||
.addDecorator(FrameDecorator)
|
||||
.add('WithdrawForm', () => (
|
||||
<Stepper
|
||||
finishedTransaction={false}
|
||||
finishedButton={<Stepper.FinishButton title="RESET" />}
|
||||
onSubmit={() => {}}
|
||||
steps={['Fill Withdraw Form', 'Review Withdraw']}
|
||||
onReset={() => {}}
|
||||
>
|
||||
<Stepper.Page dailyLimit={10} spentToday={7}>
|
||||
{ WithdrawForm }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
))
|
|
@ -1,59 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
|
||||
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
|
||||
export const DESTINATION_PARAM = 'destination'
|
||||
export const VALUE_PARAM = 'ether'
|
||||
|
||||
export const safeFieldsValidation = (values: Object) => {
|
||||
const errors = {}
|
||||
|
||||
if (Number.parseInt(values.owners, 10) < Number.parseInt(values.confirmations, 10)) {
|
||||
errors.confirmations = CONFIRMATIONS_ERROR
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
type Props = {
|
||||
limit: number,
|
||||
spentToday: number,
|
||||
}
|
||||
|
||||
const WithdrawForm = ({ limit, spentToday }: Props) => () => (
|
||||
<Block margin="md">
|
||||
<Heading tag="h2" margin="lg">
|
||||
Withdraw Funds
|
||||
</Heading>
|
||||
<Heading tag="h4" margin="lg">
|
||||
{`Daily limit ${limit} ETH (spent today: ${spentToday} ETH)`}
|
||||
</Heading>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={VALUE_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeFloat, greaterThan(0), inLimit(limit, spentToday, 'daily limit'))}
|
||||
placeholder="Amount in ETH*"
|
||||
text="Amount in ETH"
|
||||
/>
|
||||
</Block>
|
||||
<Block margin="md">
|
||||
<Field
|
||||
name={DESTINATION_PARAM}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(required, mustBeEthereumAddress)}
|
||||
placeholder="Destination*"
|
||||
text="Destination"
|
||||
/>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
||||
export default WithdrawForm
|
|
@ -1,12 +0,0 @@
|
|||
// @flow
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
|
||||
type FetchTransactions = typeof fetchTransactions
|
||||
|
||||
export type Actions = {
|
||||
fetchTransactions: FetchTransactions,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchTransactions,
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { type DailyLimit } from '~/routes/safe/store/model/dailyLimit'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { withdraw } from '~/logic/safe/safeFrontendOperations'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import WithdrawForm from './WithdrawForm'
|
||||
import Review from './Review'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Withdraw Form', 'Review Withdraw',
|
||||
]
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
safe: Safe,
|
||||
dailyLimit: DailyLimit,
|
||||
}
|
||||
|
||||
type State = {
|
||||
done: boolean,
|
||||
}
|
||||
|
||||
export const SEE_TXS_BUTTON_TEXT = 'RESET'
|
||||
|
||||
class Withdraw extends React.Component<Props, State> {
|
||||
state = {
|
||||
done: false,
|
||||
}
|
||||
|
||||
onWithdraw = async (values: Object) => {
|
||||
try {
|
||||
const { safe, userAddress, fetchTransactions } = this.props
|
||||
await withdraw(values, safe, userAddress)
|
||||
fetchTransactions(safe.get('address'))
|
||||
|
||||
this.setState({ done: true })
|
||||
} catch (error) {
|
||||
this.setState({ done: false })
|
||||
// eslint-disable-next-line
|
||||
console.log('Error while withdrawing funds ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
onReset = () => {
|
||||
this.setState({ done: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dailyLimit } = this.props
|
||||
const { done } = this.state
|
||||
const steps = getSteps()
|
||||
const finishedButton = <Stepper.FinishButton title={SEE_TXS_BUTTON_TEXT} />
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Stepper
|
||||
finishedTransaction={done}
|
||||
finishedButton={finishedButton}
|
||||
onSubmit={this.onWithdraw}
|
||||
steps={steps}
|
||||
onReset={this.onReset}
|
||||
>
|
||||
<Stepper.Page limit={dailyLimit.get('value')} spentToday={dailyLimit.get('spentToday')}>
|
||||
{ WithdrawForm }
|
||||
</Stepper.Page>
|
||||
<Stepper.Page>
|
||||
{ Review }
|
||||
</Stepper.Page>
|
||||
</Stepper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(Withdraw)
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
|
||||
export type SelectorProps = {
|
||||
userAddress: userAccountSelector,
|
||||
}
|
||||
|
||||
export default createStructuredSelector({
|
||||
userAddress: userAccountSelector,
|
||||
})
|
Loading…
Reference in New Issue