Refactor stepper to hooks
This commit is contained in:
parent
25eadc369d
commit
f9c67171a8
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Page from '~/components/layout/Page'
|
|
||||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
|
import Page from '~/components/layout/Page'
|
||||||
|
|
||||||
const centerStyle = {
|
const centerStyle = {
|
||||||
margin: 'auto 0',
|
margin: 'auto 0',
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
import Stepper from '@material-ui/core/Stepper'
|
import Stepper from '@material-ui/core/Stepper'
|
||||||
import FormStep from '@material-ui/core/Step'
|
import FormStep from '@material-ui/core/Step'
|
||||||
import StepLabel from '@material-ui/core/StepLabel'
|
import StepLabel from '@material-ui/core/StepLabel'
|
||||||
import StepContent from '@material-ui/core/StepContent'
|
import StepContent from '@material-ui/core/StepContent'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import * as React from 'react'
|
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Button from '~/components/layout/Button'
|
|
||||||
import { history } from '~/store'
|
import { history } from '~/store'
|
||||||
import Controls from './Controls'
|
import Controls from './Controls'
|
||||||
|
|
||||||
|
const { useState, useEffect } = React
|
||||||
|
|
||||||
export { default as Step } from './Step'
|
export { default as Step } from './Step'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -18,20 +19,14 @@ type Props = {
|
||||||
onSubmit: (values: Object) => Promise<void>,
|
onSubmit: (values: Object) => Promise<void>,
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
onReset?: () => void,
|
|
||||||
initialValues?: Object,
|
initialValues?: Object,
|
||||||
disabledWhenValidating?: boolean,
|
disabledWhenValidating?: boolean,
|
||||||
testId?: string,
|
testId?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
|
||||||
page: number,
|
|
||||||
values: Object,
|
|
||||||
}
|
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
children: Function,
|
children: Function,
|
||||||
prepareNextInitialProps: (values: Object) => {},
|
prepareNextInitialProps?: (values: Object) => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const transitionProps = {
|
const transitionProps = {
|
||||||
|
@ -41,151 +36,117 @@ const transitionProps = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
class GnoStepper extends React.PureComponent<Props, State> {
|
export const StepperPage = ({ children }: PageProps) => children
|
||||||
static Page = ({ children }: PageProps) => children
|
|
||||||
|
|
||||||
static FinishButton = ({
|
const GnoStepper = (props: Props) => {
|
||||||
component, to, title, ...props
|
const [page, setPage] = useState<number>(0)
|
||||||
}) => (
|
const [values, setValues] = useState<Object>({})
|
||||||
<Button component={component} to={to} variant="contained" color="primary" {...props}>
|
|
||||||
{title}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
useEffect(() => {
|
||||||
super(props)
|
if (props.initialValues) {
|
||||||
|
setValues(props.initialValues)
|
||||||
this.state = {
|
|
||||||
page: 0,
|
|
||||||
values: props.initialValues || {},
|
|
||||||
}
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props
|
||||||
|
|
||||||
|
const updateInitialProps = (newInitialProps) => {
|
||||||
|
setValues(newInitialProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
onReset = () => {
|
const getActivePageFrom = (pages: React.Node) => {
|
||||||
const { onReset, initialValues } = this.props
|
const activePageProps = getPageProps(pages)
|
||||||
if (onReset) {
|
const { children, ...restProps } = activePageProps
|
||||||
onReset()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(() => ({
|
return children({ ...restProps, updateInitialProps })
|
||||||
page: 0,
|
|
||||||
values: initialValues || {},
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageProps = (pages: React.Node): PageProps => {
|
const validate = (valuesToValidate: Object) => {
|
||||||
const { page } = this.state
|
const { children } = props
|
||||||
|
|
||||||
return React.Children.toArray(pages)[page].props
|
|
||||||
}
|
|
||||||
|
|
||||||
getActivePageFrom = (pages: React.Node) => {
|
|
||||||
const activePageProps = this.getPageProps(pages)
|
|
||||||
const { children, ...props } = activePageProps
|
|
||||||
|
|
||||||
return children({ ...props, updateInitialProps: this.updateInitialProps })
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInitialProps = (values) => {
|
|
||||||
this.setState({ values })
|
|
||||||
}
|
|
||||||
|
|
||||||
validate = (values: Object) => {
|
|
||||||
const { children } = this.props
|
|
||||||
const { page } = this.state
|
|
||||||
|
|
||||||
const activePage = React.Children.toArray(children)[page]
|
const activePage = React.Children.toArray(children)[page]
|
||||||
return activePage.props.validate ? activePage.props.validate(values) : {}
|
return activePage.props.validate ? activePage.props.validate(valuesToValidate) : {}
|
||||||
}
|
}
|
||||||
|
|
||||||
next = async (values: Object) => {
|
const next = async (formValues: Object) => {
|
||||||
const { children } = this.props
|
const { children } = props
|
||||||
const activePageProps = this.getPageProps(children)
|
const activePageProps = getPageProps(children)
|
||||||
const { prepareNextInitialProps } = activePageProps
|
const { prepareNextInitialProps } = activePageProps
|
||||||
|
|
||||||
let pageInitialProps
|
let pageInitialProps
|
||||||
if (prepareNextInitialProps) {
|
if (prepareNextInitialProps) {
|
||||||
pageInitialProps = await prepareNextInitialProps(values)
|
pageInitialProps = await prepareNextInitialProps(formValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalValues = { ...values, ...pageInitialProps }
|
const finalValues = { ...formValues, ...pageInitialProps }
|
||||||
this.setState(state => ({
|
|
||||||
page: Math.min(state.page + 1, React.Children.count(children) - 1),
|
setValues(finalValues)
|
||||||
values: finalValues,
|
setPage(Math.min(page + 1, React.Children.count(children) - 1))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previous = () => {
|
const previous = () => {
|
||||||
const { page } = this.state
|
|
||||||
|
|
||||||
const firstPage = page === 0
|
const firstPage = page === 0
|
||||||
if (firstPage) {
|
if (firstPage) {
|
||||||
return history.goBack()
|
return history.goBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setState(state => ({
|
return setPage(Math.max(page - 1, 0))
|
||||||
page: Math.max(state.page - 1, 0),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = async (values: Object) => {
|
const handleSubmit = async (formValues: Object) => {
|
||||||
const { children, onSubmit } = this.props
|
const { children, onSubmit } = props
|
||||||
const { page } = this.state
|
|
||||||
const isLastPage = page === React.Children.count(children) - 1
|
const isLastPage = page === React.Children.count(children) - 1
|
||||||
if (isLastPage) {
|
if (isLastPage) {
|
||||||
return onSubmit(values)
|
return onSubmit(formValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.next(values)
|
return next(formValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLastPage = (page) => {
|
const isLastPage = (pageNumber) => {
|
||||||
const { steps } = this.props
|
const { steps } = props
|
||||||
return page === steps.length - 1
|
return pageNumber === steps.length - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const {
|
||||||
const {
|
steps, children, classes, disabledWhenValidating = false, testId,
|
||||||
steps, children, classes, disabledWhenValidating = false, testId,
|
} = props
|
||||||
} = this.props
|
const activePage = getActivePageFrom(children)
|
||||||
const { page, values } = this.state
|
const lastPage = isLastPage(page)
|
||||||
const activePage = this.getActivePageFrom(children)
|
const penultimate = isLastPage(page + 1)
|
||||||
const lastPage = this.isLastPage(page)
|
|
||||||
const penultimate = this.isLastPage(page + 1)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<GnoForm onSubmit={this.handleSubmit} initialValues={values} validation={this.validate} testId={testId}>
|
<GnoForm onSubmit={handleSubmit} initialValues={values} validation={validate} testId={testId}>
|
||||||
{(submitting: boolean, validating: boolean, ...rest: any) => {
|
{(submitting: boolean, validating: boolean, ...rest: any) => {
|
||||||
const disabled = disabledWhenValidating ? submitting || validating : submitting
|
const disabled = disabledWhenValidating ? submitting || validating : submitting
|
||||||
const controls = (
|
const controls = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<Controls
|
<Controls
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onPrevious={this.previous}
|
onPrevious={previous}
|
||||||
firstPage={page === 0}
|
firstPage={page === 0}
|
||||||
lastPage={lastPage}
|
lastPage={lastPage}
|
||||||
penultimate={penultimate}
|
penultimate={penultimate}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stepper classes={{ root: classes.root }} activeStep={page} orientation="vertical">
|
<Stepper classes={{ root: classes.root }} activeStep={page} orientation="vertical">
|
||||||
{steps.map(label => (
|
{steps.map(label => (
|
||||||
<FormStep key={label}>
|
<FormStep key={label}>
|
||||||
<StepLabel>{label}</StepLabel>
|
<StepLabel>{label}</StepLabel>
|
||||||
<StepContent TransitionProps={transitionProps}>{activePage(controls, ...rest)}</StepContent>
|
<StepContent TransitionProps={transitionProps}>{activePage(controls, ...rest)}</StepContent>
|
||||||
</FormStep>
|
</FormStep>
|
||||||
))}
|
))}
|
||||||
</Stepper>
|
</Stepper>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</GnoForm>
|
</GnoForm>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import ChevronLeft from '@material-ui/icons/ChevronLeft'
|
import ChevronLeft from '@material-ui/icons/ChevronLeft'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import Stepper from '~/components/Stepper'
|
import Stepper, { StepperPage } from '~/components/Stepper'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Heading from '~/components/layout/Heading'
|
import Heading from '~/components/layout/Heading'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
@ -46,11 +46,11 @@ const Layout = ({
|
||||||
<Heading tag="h2">Load existing Safe</Heading>
|
<Heading tag="h2">Load existing Safe</Heading>
|
||||||
</Row>
|
</Row>
|
||||||
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} initialValues={initialValues} testId="load-safe-form">
|
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} initialValues={initialValues} testId="load-safe-form">
|
||||||
<Stepper.Page validate={safeFieldsValidation}>{DetailsForm}</Stepper.Page>
|
<StepperPage validate={safeFieldsValidation}>{DetailsForm}</StepperPage>
|
||||||
<Stepper.Page network={network}>{OwnerList}</Stepper.Page>
|
<StepperPage network={network}>{OwnerList}</StepperPage>
|
||||||
<Stepper.Page network={network} userAddress={userAddress}>
|
<StepperPage network={network} userAddress={userAddress}>
|
||||||
{ReviewInformation}
|
{ReviewInformation}
|
||||||
</Stepper.Page>
|
</StepperPage>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</Block>
|
</Block>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import ChevronLeft from '@material-ui/icons/ChevronLeft'
|
import ChevronLeft from '@material-ui/icons/ChevronLeft'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import Stepper from '~/components/Stepper'
|
import Stepper, { StepperPage } from '~/components/Stepper'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Heading from '~/components/layout/Heading'
|
import Heading from '~/components/layout/Heading'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
@ -60,9 +60,9 @@ const Layout = ({
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
testId="create-safe-form"
|
testId="create-safe-form"
|
||||||
>
|
>
|
||||||
<Stepper.Page>{SafeNameField}</Stepper.Page>
|
<StepperPage>{SafeNameField}</StepperPage>
|
||||||
<Stepper.Page>{SafeOwnersFields}</Stepper.Page>
|
<StepperPage>{SafeOwnersFields}</StepperPage>
|
||||||
<Stepper.Page network={network}>{Review}</Stepper.Page>
|
<StepperPage network={network}>{Review}</StepperPage>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</Block>
|
</Block>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -15,7 +15,9 @@ import Button from '~/components/layout/Button'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import { FIELD_CONFIRMATIONS, getOwnerNameBy, getOwnerAddressBy, getNumOwnersFrom } from '~/routes/open/components/fields'
|
import {
|
||||||
|
FIELD_CONFIRMATIONS, getOwnerNameBy, getOwnerAddressBy, getNumOwnersFrom,
|
||||||
|
} from '~/routes/open/components/fields'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
|
@ -183,8 +185,8 @@ const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node
|
||||||
errors={errors}
|
errors={errors}
|
||||||
updateInitialProps={updateInitialProps}
|
updateInitialProps={updateInitialProps}
|
||||||
values={values}
|
values={values}
|
||||||
/>
|
/>
|
||||||
{console.log('vals one level up', values)}
|
{console.log('vals one level up', values)}
|
||||||
</OpenPaper>
|
</OpenPaper>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,4 +14,4 @@ export const getAddressValidators = (addresses: string[], position: number) => {
|
||||||
copy.pop()
|
copy.pop()
|
||||||
|
|
||||||
return composeValidators(required, mustBeEthereumAddress, uniqueAddress(copy))
|
return composeValidators(required, mustBeEthereumAddress, uniqueAddress(copy))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +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 { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from '~/routes/safe/components/SendToken/SendTokenForm/index'
|
|
||||||
|
|
||||||
type FormProps = {
|
|
||||||
values: Object,
|
|
||||||
submitting: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
symbol: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const spinnerStyle = {
|
|
||||||
minHeight: '50px',
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReviewTx = ({ symbol }: Props) => (controls: React.Node, { values, submitting }: FormProps) => (
|
|
||||||
<OpenPaper controls={controls}>
|
|
||||||
<Heading tag="h2">Review the move token funds</Heading>
|
|
||||||
<Paragraph align="left">
|
|
||||||
<Bold>Destination: </Bold>
|
|
||||||
{' '}
|
|
||||||
{values[TKN_DESTINATION_PARAM]}
|
|
||||||
</Paragraph>
|
|
||||||
<Paragraph align="left">
|
|
||||||
<Bold>{`Amount to transfer: ${values[TKN_VALUE_PARAM]} ${symbol}`}</Bold>
|
|
||||||
</Paragraph>
|
|
||||||
<Block style={spinnerStyle}>{submitting && <CircularProgress size={50} />}</Block>
|
|
||||||
</OpenPaper>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default ReviewTx
|
|
|
@ -1,53 +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 OpenPaper from '~/components/Stepper/OpenPaper'
|
|
||||||
import Heading from '~/components/layout/Heading'
|
|
||||||
|
|
||||||
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
|
|
||||||
|
|
||||||
export const TKN_DESTINATION_PARAM = 'tknDestination'
|
|
||||||
export const TKN_VALUE_PARAM = 'tknValue'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
funds: string,
|
|
||||||
symbol: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const SendTokenForm = ({ funds, symbol }: Props) => (controls: React.Node) => (
|
|
||||||
<OpenPaper controls={controls}>
|
|
||||||
<Heading tag="h2" margin="lg">
|
|
||||||
Send tokens Transaction
|
|
||||||
</Heading>
|
|
||||||
<Heading tag="h4" margin="lg">
|
|
||||||
{`Available tokens: ${funds} ${symbol}`}
|
|
||||||
</Heading>
|
|
||||||
<Block margin="md">
|
|
||||||
<Field
|
|
||||||
name={TKN_DESTINATION_PARAM}
|
|
||||||
component={TextField}
|
|
||||||
type="text"
|
|
||||||
validate={composeValidators(required, mustBeEthereumAddress)}
|
|
||||||
placeholder="Destination*"
|
|
||||||
text="Destination"
|
|
||||||
/>
|
|
||||||
</Block>
|
|
||||||
<Block margin="md">
|
|
||||||
<Field
|
|
||||||
name={TKN_VALUE_PARAM}
|
|
||||||
component={TextField}
|
|
||||||
type="text"
|
|
||||||
validate={composeValidators(required, mustBeFloat, greaterThan(0), inLimit(Number(funds), 0, 'available balance', symbol))}
|
|
||||||
placeholder="Amount of tokens*"
|
|
||||||
text="Amount of Tokens"
|
|
||||||
/>
|
|
||||||
</Block>
|
|
||||||
</OpenPaper>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default SendTokenForm
|
|
|
@ -1,10 +0,0 @@
|
||||||
// @flow
|
|
||||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
|
||||||
|
|
||||||
export type Actions = {
|
|
||||||
fetchTransactions: typeof fetchTransactions,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fetchTransactions,
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as React from 'react'
|
|
||||||
import { BigNumber } from 'bignumber.js'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import Stepper from '~/components/Stepper'
|
|
||||||
import { sleep } from '~/utils/timer'
|
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
|
||||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
|
||||||
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
|
||||||
import { toNative } from '~/logic/wallets/tokens'
|
|
||||||
import { createTransaction } from '~/logic/safe/safeFrontendOperations'
|
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
|
||||||
import actions, { type Actions } from './actions'
|
|
||||||
import selector, { type SelectorProps } from './selector'
|
|
||||||
import SendTokenForm, { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from './SendTokenForm'
|
|
||||||
import ReviewTx from './ReviewTx'
|
|
||||||
|
|
||||||
const getSteps = () => ['Fill Move Token form', 'Review Move Token form']
|
|
||||||
|
|
||||||
type Props = SelectorProps &
|
|
||||||
Actions & {
|
|
||||||
safe: Safe,
|
|
||||||
token: Token,
|
|
||||||
onReset: () => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
done: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SEE_TXS_BUTTON_TEXT = 'VISIT TXS'
|
|
||||||
|
|
||||||
const getTransferData = async (tokenAddress: string, to: string, amount: BigNumber) => {
|
|
||||||
const StandardToken = await getStandardTokenContract()
|
|
||||||
const myToken = await StandardToken.at(tokenAddress)
|
|
||||||
|
|
||||||
return myToken.contract.transfer(to, amount).encodeABI()
|
|
||||||
}
|
|
||||||
|
|
||||||
const processTokenTransfer = async (safe: Safe, token: Token, to: string, amount: string, userAddress: string) => {
|
|
||||||
const safeAddress = safe.get('address')
|
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
|
||||||
const nonce = await gnosisSafe.nonce()
|
|
||||||
const symbol = token.get('symbol')
|
|
||||||
const name = `Send ${amount} ${symbol} to ${to}`
|
|
||||||
const value = isEther(symbol) ? amount : '0'
|
|
||||||
const tokenAddress = token.get('address')
|
|
||||||
const destination = isEther(symbol) ? to : tokenAddress
|
|
||||||
const data = isEther(symbol)
|
|
||||||
? EMPTY_DATA
|
|
||||||
: await getTransferData(tokenAddress, to, toNative(amount, token.get('decimals')))
|
|
||||||
|
|
||||||
return createTransaction(safe, name, destination, value, nonce, userAddress, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SendToken extends React.Component<Props, State> {
|
|
||||||
state = {
|
|
||||||
done: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
onTransaction = async (values: Object) => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
safe, token, userAddress, fetchTransactions,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const amount = values[TKN_VALUE_PARAM]
|
|
||||||
const destination = values[TKN_DESTINATION_PARAM]
|
|
||||||
|
|
||||||
await processTokenTransfer(safe, token, destination, amount, userAddress)
|
|
||||||
await sleep(1500)
|
|
||||||
fetchTransactions(safe.get('address'))
|
|
||||||
this.setState({ done: true })
|
|
||||||
} catch (error) {
|
|
||||||
this.setState({ done: false })
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log('Error while moving ERC20 token funds ' + error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onReset = () => {
|
|
||||||
const { onReset } = this.props
|
|
||||||
|
|
||||||
this.setState({ done: false })
|
|
||||||
onReset() // This is for show the TX list component
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { done } = this.state
|
|
||||||
const { token } = this.props
|
|
||||||
const steps = getSteps()
|
|
||||||
const finishedButton = <Stepper.FinishButton title={SEE_TXS_BUTTON_TEXT} />
|
|
||||||
const symbol = token.get('symbol')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Stepper
|
|
||||||
finishedTransaction={done}
|
|
||||||
finishedButton={finishedButton}
|
|
||||||
onSubmit={this.onTransaction}
|
|
||||||
steps={steps}
|
|
||||||
onReset={this.onReset}
|
|
||||||
>
|
|
||||||
<Stepper.Page funds={token.get('funds')} symbol={symbol}>
|
|
||||||
{SendTokenForm}
|
|
||||||
</Stepper.Page>
|
|
||||||
<Stepper.Page symbol={symbol}>{ReviewTx}</Stepper.Page>
|
|
||||||
</Stepper>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
selector,
|
|
||||||
actions,
|
|
||||||
)(SendToken)
|
|
|
@ -1,11 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { createStructuredSelector } from 'reselect'
|
|
||||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
|
||||||
|
|
||||||
export type SelectorProps = {
|
|
||||||
userAddress: typeof userAccountSelector,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createStructuredSelector<Object, *>({
|
|
||||||
userAddress: userAccountSelector,
|
|
||||||
})
|
|
|
@ -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 OpenPaper from '~/components/Stepper/OpenPaper'
|
|
||||||
import Heading from '~/components/layout/Heading'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
|
||||||
import { THRESHOLD_PARAM } from '~/routes/safe/components/Threshold/ThresholdForm'
|
|
||||||
|
|
||||||
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 Threshold operation</Heading>
|
|
||||||
<Paragraph align="left">
|
|
||||||
<Bold>The new threshold will be: </Bold>
|
|
||||||
{' '}
|
|
||||||
{values[THRESHOLD_PARAM]}
|
|
||||||
</Paragraph>
|
|
||||||
<Block style={spinnerStyle}>
|
|
||||||
{ submitting && <CircularProgress size={50} /> }
|
|
||||||
</Block>
|
|
||||||
</OpenPaper>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Review
|
|
|
@ -1,46 +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, maxValue, mustBeInteger, required,
|
|
||||||
} from '~/components/forms/validator'
|
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
|
||||||
|
|
||||||
export const THRESHOLD_PARAM = 'threshold'
|
|
||||||
|
|
||||||
type ThresholdProps = {
|
|
||||||
numOwners: number,
|
|
||||||
safe: Safe,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThresholdForm = ({ numOwners, safe }: ThresholdProps) => (controls: React.Node) => (
|
|
||||||
<OpenPaper controls={controls}>
|
|
||||||
<Heading tag="h2" margin="lg">
|
|
||||||
{'Change safe\'s threshold'}
|
|
||||||
</Heading>
|
|
||||||
<Heading tag="h4" margin="lg">
|
|
||||||
{`Safe's owners: ${numOwners} and Safe's threshold: ${safe.get('threshold')}`}
|
|
||||||
</Heading>
|
|
||||||
<Block margin="md">
|
|
||||||
<Field
|
|
||||||
name={THRESHOLD_PARAM}
|
|
||||||
component={TextField}
|
|
||||||
type="text"
|
|
||||||
validate={composeValidators(
|
|
||||||
required,
|
|
||||||
mustBeInteger,
|
|
||||||
minValue(1),
|
|
||||||
maxValue(numOwners),
|
|
||||||
)}
|
|
||||||
placeholder="New threshold"
|
|
||||||
text="Safe's threshold"
|
|
||||||
/>
|
|
||||||
</Block>
|
|
||||||
</OpenPaper>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default ThresholdForm
|
|
|
@ -1,10 +0,0 @@
|
||||||
// @flow
|
|
||||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
|
||||||
|
|
||||||
export type Actions = {
|
|
||||||
fetchTransactions: typeof fetchTransactions,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fetchTransactions,
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as React from 'react'
|
|
||||||
import Stepper from '~/components/Stepper'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { createTransaction } from '~/logic/safe/safeFrontendOperations'
|
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
|
||||||
import ThresholdForm, { THRESHOLD_PARAM } from './ThresholdForm'
|
|
||||||
import selector, { type SelectorProps } from './selector'
|
|
||||||
import actions, { type Actions } from './actions'
|
|
||||||
import Review from './Review'
|
|
||||||
|
|
||||||
type Props = SelectorProps & Actions & {
|
|
||||||
numOwners: number,
|
|
||||||
safe: Safe,
|
|
||||||
onReset: () => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSteps = () => [
|
|
||||||
'Fill Change threshold Form', 'Review change threshold operation',
|
|
||||||
]
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
done: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CHANGE_THRESHOLD_RESET_BUTTON_TEXT = 'SEE TXs'
|
|
||||||
|
|
||||||
class Threshold extends React.PureComponent<Props, State> {
|
|
||||||
state = {
|
|
||||||
done: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
onThreshold = async (values: Object) => {
|
|
||||||
try {
|
|
||||||
const { safe, userAddress, fetchTransactions } = this.props // , fetchThreshold } = this.props
|
|
||||||
const newThreshold = values[THRESHOLD_PARAM]
|
|
||||||
const safeAddress = safe.get('address')
|
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
|
||||||
const nonce = await gnosisSafe.nonce()
|
|
||||||
const data = gnosisSafe.contract.changeThreshold(newThreshold).encodeABI()
|
|
||||||
await createTransaction(safe, `Change Safe's threshold [${nonce}]`, safeAddress, '0', nonce, userAddress, data)
|
|
||||||
await fetchTransactions(safeAddress)
|
|
||||||
this.setState({ done: true })
|
|
||||||
} catch (error) {
|
|
||||||
this.setState({ done: false })
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log('Error while changing threshold ' + error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onReset = () => {
|
|
||||||
const { onReset } = this.props
|
|
||||||
this.setState({ done: false })
|
|
||||||
|
|
||||||
onReset()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { numOwners, safe } = 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.onThreshold}
|
|
||||||
steps={steps}
|
|
||||||
onReset={this.onReset}
|
|
||||||
>
|
|
||||||
<Stepper.Page numOwners={numOwners} safe={safe}>
|
|
||||||
{ ThresholdForm }
|
|
||||||
</Stepper.Page>
|
|
||||||
<Stepper.Page>
|
|
||||||
{ Review }
|
|
||||||
</Stepper.Page>
|
|
||||||
</Stepper>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(selector, actions)(Threshold)
|
|
|
@ -1,11 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { createStructuredSelector } from 'reselect'
|
|
||||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
|
||||||
|
|
||||||
export type SelectorProps = {
|
|
||||||
userAddress: typeof userAccountSelector,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createStructuredSelector<Object, *>({
|
|
||||||
userAddress: userAccountSelector,
|
|
||||||
})
|
|
Loading…
Reference in New Issue