From f9c67171a80f5de1e9753ca530f172340e44d95c Mon Sep 17 00:00:00 2001 From: mmv Date: Thu, 18 Jul 2019 18:21:02 +0400 Subject: [PATCH] Refactor stepper to hooks --- src/components/Loader/index.jsx | 2 +- src/components/Stepper/index.jsx | 195 +++++++----------- src/routes/load/components/Layout.jsx | 10 +- src/routes/open/components/Layout.jsx | 8 +- .../SafeOwnersConfirmationsForm/index.jsx | 8 +- .../SafeOwnersConfirmationsForm/validators.js | 2 +- .../components/SendToken/ReviewTx/index.jsx | 39 ---- .../SendToken/SendTokenForm/index.jsx | 53 ----- .../safe/components/SendToken/actions.js | 10 - .../safe/components/SendToken/index.jsx | 119 ----------- .../safe/components/SendToken/selector.js | 11 - .../components/Threshold/Review/index.jsx | 34 --- .../Threshold/ThresholdForm/index.jsx | 46 ----- .../safe/components/Threshold/actions.js | 10 - .../safe/components/Threshold/index.jsx | 86 -------- .../safe/components/Threshold/selector.js | 11 - 16 files changed, 94 insertions(+), 550 deletions(-) delete mode 100644 src/routes/safe/components/SendToken/ReviewTx/index.jsx delete mode 100644 src/routes/safe/components/SendToken/SendTokenForm/index.jsx delete mode 100644 src/routes/safe/components/SendToken/actions.js delete mode 100644 src/routes/safe/components/SendToken/index.jsx delete mode 100644 src/routes/safe/components/SendToken/selector.js delete mode 100644 src/routes/safe/components/Threshold/Review/index.jsx delete mode 100644 src/routes/safe/components/Threshold/ThresholdForm/index.jsx delete mode 100644 src/routes/safe/components/Threshold/actions.js delete mode 100644 src/routes/safe/components/Threshold/index.jsx delete mode 100644 src/routes/safe/components/Threshold/selector.js diff --git a/src/components/Loader/index.jsx b/src/components/Loader/index.jsx index 5ae3f7b9..78906e6b 100644 --- a/src/components/Loader/index.jsx +++ b/src/components/Loader/index.jsx @@ -1,7 +1,7 @@ // @flow import * as React from 'react' -import Page from '~/components/layout/Page' import CircularProgress from '@material-ui/core/CircularProgress' +import Page from '~/components/layout/Page' const centerStyle = { margin: 'auto 0', diff --git a/src/components/Stepper/index.jsx b/src/components/Stepper/index.jsx index 7d3de182..9c69ec5e 100644 --- a/src/components/Stepper/index.jsx +++ b/src/components/Stepper/index.jsx @@ -1,16 +1,17 @@ // @flow +import * as React from 'react' import Stepper from '@material-ui/core/Stepper' import FormStep from '@material-ui/core/Step' import StepLabel from '@material-ui/core/StepLabel' import StepContent from '@material-ui/core/StepContent' import { withStyles } from '@material-ui/core/styles' -import * as React from 'react' import GnoForm from '~/components/forms/GnoForm' import Hairline from '~/components/layout/Hairline' -import Button from '~/components/layout/Button' import { history } from '~/store' import Controls from './Controls' +const { useState, useEffect } = React + export { default as Step } from './Step' type Props = { @@ -18,20 +19,14 @@ type Props = { onSubmit: (values: Object) => Promise, children: React.Node, classes: Object, - onReset?: () => void, initialValues?: Object, disabledWhenValidating?: boolean, testId?: string, } -type State = { - page: number, - values: Object, -} - type PageProps = { children: Function, - prepareNextInitialProps: (values: Object) => {}, + prepareNextInitialProps?: (values: Object) => {}, } const transitionProps = { @@ -41,151 +36,117 @@ const transitionProps = { }, } -class GnoStepper extends React.PureComponent { - static Page = ({ children }: PageProps) => children +export const StepperPage = ({ children }: PageProps) => children - static FinishButton = ({ - component, to, title, ...props - }) => ( - - ) +const GnoStepper = (props: Props) => { + const [page, setPage] = useState(0) + const [values, setValues] = useState({}) - constructor(props: Props) { - super(props) - - this.state = { - page: 0, - values: props.initialValues || {}, + useEffect(() => { + if (props.initialValues) { + setValues(props.initialValues) } + }, []) + + const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props + + const updateInitialProps = (newInitialProps) => { + setValues(newInitialProps) } - onReset = () => { - const { onReset, initialValues } = this.props - if (onReset) { - onReset() - } + const getActivePageFrom = (pages: React.Node) => { + const activePageProps = getPageProps(pages) + const { children, ...restProps } = activePageProps - this.setState(() => ({ - page: 0, - values: initialValues || {}, - })) + return children({ ...restProps, updateInitialProps }) } - getPageProps = (pages: React.Node): PageProps => { - const { page } = this.state - - 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 validate = (valuesToValidate: Object) => { + const { children } = props 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 { children } = this.props - const activePageProps = this.getPageProps(children) + const next = async (formValues: Object) => { + const { children } = props + const activePageProps = getPageProps(children) const { prepareNextInitialProps } = activePageProps let pageInitialProps if (prepareNextInitialProps) { - pageInitialProps = await prepareNextInitialProps(values) + pageInitialProps = await prepareNextInitialProps(formValues) } - const finalValues = { ...values, ...pageInitialProps } - this.setState(state => ({ - page: Math.min(state.page + 1, React.Children.count(children) - 1), - values: finalValues, - })) + const finalValues = { ...formValues, ...pageInitialProps } + + setValues(finalValues) + setPage(Math.min(page + 1, React.Children.count(children) - 1)) } - previous = () => { - const { page } = this.state - + const previous = () => { const firstPage = page === 0 if (firstPage) { return history.goBack() } - return this.setState(state => ({ - page: Math.max(state.page - 1, 0), - })) + return setPage(Math.max(page - 1, 0)) } - handleSubmit = async (values: Object) => { - const { children, onSubmit } = this.props - const { page } = this.state + const handleSubmit = async (formValues: Object) => { + const { children, onSubmit } = props const isLastPage = page === React.Children.count(children) - 1 if (isLastPage) { - return onSubmit(values) + return onSubmit(formValues) } - return this.next(values) + return next(formValues) } - isLastPage = (page) => { - const { steps } = this.props - return page === steps.length - 1 + const isLastPage = (pageNumber) => { + const { steps } = props + return pageNumber === steps.length - 1 } - render() { - const { - steps, children, classes, disabledWhenValidating = false, testId, - } = this.props - const { page, values } = this.state - const activePage = this.getActivePageFrom(children) - const lastPage = this.isLastPage(page) - const penultimate = this.isLastPage(page + 1) + const { + steps, children, classes, disabledWhenValidating = false, testId, + } = props + const activePage = getActivePageFrom(children) + const lastPage = isLastPage(page) + const penultimate = isLastPage(page + 1) - return ( - - - {(submitting: boolean, validating: boolean, ...rest: any) => { - const disabled = disabledWhenValidating ? submitting || validating : submitting - const controls = ( - - - - - ) + return ( + + + {(submitting: boolean, validating: boolean, ...rest: any) => { + const disabled = disabledWhenValidating ? submitting || validating : submitting + const controls = ( + + + + + ) - return ( - - {steps.map(label => ( - - {label} - {activePage(controls, ...rest)} - - ))} - - ) - }} - - - ) - } + return ( + + {steps.map(label => ( + + {label} + {activePage(controls, ...rest)} + + ))} + + ) + }} + + + ) } const styles = { diff --git a/src/routes/load/components/Layout.jsx b/src/routes/load/components/Layout.jsx index 0fa0a213..6e9ab43b 100644 --- a/src/routes/load/components/Layout.jsx +++ b/src/routes/load/components/Layout.jsx @@ -2,7 +2,7 @@ import * as React from 'react' import ChevronLeft from '@material-ui/icons/ChevronLeft' 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 Heading from '~/components/layout/Heading' import Row from '~/components/layout/Row' @@ -46,11 +46,11 @@ const Layout = ({ Load existing Safe - {DetailsForm} - {OwnerList} - + {DetailsForm} + {OwnerList} + {ReviewInformation} - + ) : ( diff --git a/src/routes/open/components/Layout.jsx b/src/routes/open/components/Layout.jsx index 72f5d5a9..663bad9f 100644 --- a/src/routes/open/components/Layout.jsx +++ b/src/routes/open/components/Layout.jsx @@ -2,7 +2,7 @@ import * as React from 'react' import ChevronLeft from '@material-ui/icons/ChevronLeft' 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 Heading from '~/components/layout/Heading' import Row from '~/components/layout/Row' @@ -60,9 +60,9 @@ const Layout = ({ initialValues={initialValues} testId="create-safe-form" > - {SafeNameField} - {SafeOwnersFields} - {Review} + {SafeNameField} + {SafeOwnersFields} + {Review} ) : ( diff --git a/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx b/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx index d57af2c5..ca168489 100644 --- a/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx +++ b/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx @@ -15,7 +15,9 @@ import Button from '~/components/layout/Button' import Row from '~/components/layout/Row' import Img from '~/components/layout/Img' 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 OpenPaper from '~/components/Stepper/OpenPaper' import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor' @@ -183,8 +185,8 @@ const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node errors={errors} updateInitialProps={updateInitialProps} values={values} - /> - {console.log('vals one level up', values)} + /> + {console.log('vals one level up', values)} ) diff --git a/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js b/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js index c2e8e8fb..97f877d7 100644 --- a/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js +++ b/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js @@ -14,4 +14,4 @@ export const getAddressValidators = (addresses: string[], position: number) => { copy.pop() return composeValidators(required, mustBeEthereumAddress, uniqueAddress(copy)) -} \ No newline at end of file +} diff --git a/src/routes/safe/components/SendToken/ReviewTx/index.jsx b/src/routes/safe/components/SendToken/ReviewTx/index.jsx deleted file mode 100644 index e2a41932..00000000 --- a/src/routes/safe/components/SendToken/ReviewTx/index.jsx +++ /dev/null @@ -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) => ( - - Review the move token funds - - Destination: - {' '} - {values[TKN_DESTINATION_PARAM]} - - - {`Amount to transfer: ${values[TKN_VALUE_PARAM]} ${symbol}`} - - {submitting && } - -) - -export default ReviewTx diff --git a/src/routes/safe/components/SendToken/SendTokenForm/index.jsx b/src/routes/safe/components/SendToken/SendTokenForm/index.jsx deleted file mode 100644 index 1095a7fe..00000000 --- a/src/routes/safe/components/SendToken/SendTokenForm/index.jsx +++ /dev/null @@ -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) => ( - - - Send tokens Transaction - - - {`Available tokens: ${funds} ${symbol}`} - - - - - - - - -) - -export default SendTokenForm diff --git a/src/routes/safe/components/SendToken/actions.js b/src/routes/safe/components/SendToken/actions.js deleted file mode 100644 index 681ad469..00000000 --- a/src/routes/safe/components/SendToken/actions.js +++ /dev/null @@ -1,10 +0,0 @@ -// @flow -import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' - -export type Actions = { - fetchTransactions: typeof fetchTransactions, -} - -export default { - fetchTransactions, -} diff --git a/src/routes/safe/components/SendToken/index.jsx b/src/routes/safe/components/SendToken/index.jsx deleted file mode 100644 index 84899436..00000000 --- a/src/routes/safe/components/SendToken/index.jsx +++ /dev/null @@ -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 { - 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 = - const symbol = token.get('symbol') - - return ( - - - - {SendTokenForm} - - {ReviewTx} - - - ) - } -} - -export default connect( - selector, - actions, -)(SendToken) diff --git a/src/routes/safe/components/SendToken/selector.js b/src/routes/safe/components/SendToken/selector.js deleted file mode 100644 index 8701f74b..00000000 --- a/src/routes/safe/components/SendToken/selector.js +++ /dev/null @@ -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({ - userAddress: userAccountSelector, -}) diff --git a/src/routes/safe/components/Threshold/Review/index.jsx b/src/routes/safe/components/Threshold/Review/index.jsx deleted file mode 100644 index 459ca795..00000000 --- a/src/routes/safe/components/Threshold/Review/index.jsx +++ /dev/null @@ -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) => ( - - Review the Threshold operation - - The new threshold will be: - {' '} - {values[THRESHOLD_PARAM]} - - - { submitting && } - - -) - -export default Review diff --git a/src/routes/safe/components/Threshold/ThresholdForm/index.jsx b/src/routes/safe/components/Threshold/ThresholdForm/index.jsx deleted file mode 100644 index 9c6b14b5..00000000 --- a/src/routes/safe/components/Threshold/ThresholdForm/index.jsx +++ /dev/null @@ -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) => ( - - - {'Change safe\'s threshold'} - - - {`Safe's owners: ${numOwners} and Safe's threshold: ${safe.get('threshold')}`} - - - - - -) - -export default ThresholdForm diff --git a/src/routes/safe/components/Threshold/actions.js b/src/routes/safe/components/Threshold/actions.js deleted file mode 100644 index 681ad469..00000000 --- a/src/routes/safe/components/Threshold/actions.js +++ /dev/null @@ -1,10 +0,0 @@ -// @flow -import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' - -export type Actions = { - fetchTransactions: typeof fetchTransactions, -} - -export default { - fetchTransactions, -} diff --git a/src/routes/safe/components/Threshold/index.jsx b/src/routes/safe/components/Threshold/index.jsx deleted file mode 100644 index 1f698aa2..00000000 --- a/src/routes/safe/components/Threshold/index.jsx +++ /dev/null @@ -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 { - 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 = - - return ( - - - - { ThresholdForm } - - - { Review } - - - - ) - } -} - -export default connect(selector, actions)(Threshold) diff --git a/src/routes/safe/components/Threshold/selector.js b/src/routes/safe/components/Threshold/selector.js deleted file mode 100644 index 8701f74b..00000000 --- a/src/routes/safe/components/Threshold/selector.js +++ /dev/null @@ -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({ - userAddress: userAccountSelector, -})