diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.jsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.jsx index 7a72c8e0..4a0af47d 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.jsx @@ -34,6 +34,22 @@ type Props = { } type ActiveScreen = 'selectOwner' | 'selectThreshold' | 'reviewAddOwner' +export const sendAddOwner = async ( + values: Object, + safeAddress: string, + owners: List, + openSnackbar: Fuction, + createTransaction: Function, +) => { + const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) + const txData = gnosisSafe.contract.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI() + + const txHash = await createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar) + if (txHash) { + setOwners(safeAddress, owners.push(makeOwner({ name: values.ownerName, address: values.ownerAddress }))) + } +} + const AddOwner = ({ onClose, isOpen, @@ -84,12 +100,11 @@ const AddOwner = ({ {({ openSnackbar }) => { const onAddOwner = async () => { onClose() - const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) - const txData = gnosisSafe.contract.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI() - - const txHash = await createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar) - if (txHash) { - setOwners(safeAddress, owners.push(makeOwner({ name: values.ownerName, address: values.ownerAddress }))) + try { + sendAddOwner(values, safeAddress, owners, openSnackbar, createTransaction) + } catch (error) { + // eslint-disable-next-line + console.log('Error while removing an owner ' + error) } } diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.jsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.jsx index 8d049f51..92aa347f 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.jsx @@ -8,6 +8,7 @@ import { type Owner, makeOwner } from '~/routes/safe/store/models/owner' import { setOwners } from '~/logic/safe/utils' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import CheckOwner from './screens/CheckOwner' +import ThresholdForm from './screens/ThresholdForm' import { withStyles } from '@material-ui/core/styles' const styles = () => ({ @@ -34,6 +35,30 @@ type Props = { } type ActiveScreen = 'checkOwner' | 'selectThreshold' | 'reviewRemoveOwner' +const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001' + +export const sendRemoveOwner = async ( + values: Object, + safeAddress: string, + ownerAddressToRemove: string, + ownerNameToRemove: string, + owners: List, + openSnackbar: Function, + createTransaction: Function, +) => { + const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) + const storedOwners = await gnosisSafe.getOwners() + const index = storedOwners.findIndex(ownerAddress => ownerAddress === ownerAddressToRemove) + const prevAddress = index === 0 ? SENTINEL_ADDRESS : storedOwners[index - 1] + const txData = gnosisSafe.contract.methods.removeOwner(prevAddress, ownerAddressToRemove, values.threshold).encodeABI() + const text = `Remove Owner ${ownerNameToRemove} (${ownerAddressToRemove})` + + const txHash = createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar) + if (txHash) { + setOwners(safeAddress, owners.filter(o => o.address !== ownerAddressToRemove)) + } +} + const RemoveOwner = ({ onClose, isOpen, @@ -81,7 +106,14 @@ const RemoveOwner = ({ {({ openSnackbar }) => { - const onRemoveOwner = async () => { + const onRemoveOwner = () => { + onClose() + try { + sendRemoveOwner(values, safeAddress, ownerAddress, ownerName, owners, openSnackbar, createTransaction) + } catch (error) { + // eslint-disable-next-line + console.log('Error while removing an owner ' + error) + } } return ( @@ -102,6 +134,15 @@ const RemoveOwner = ({ onSubmit={ownerSubmitted} /> )} + {activeScreen === 'selectThreshold' && ( + + )} ) diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.jsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.jsx new file mode 100644 index 00000000..9d1671af --- /dev/null +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.jsx @@ -0,0 +1,139 @@ +// @flow +import React from 'react' +import { List } from 'immutable' +import { withStyles } from '@material-ui/core/styles' +import Close from '@material-ui/icons/Close' +import IconButton from '@material-ui/core/IconButton' +import SelectField from '~/components/forms/SelectField' +import MenuItem from '@material-ui/core/MenuItem' +import Paragraph from '~/components/layout/Paragraph' +import Row from '~/components/layout/Row' +import GnoForm from '~/components/forms/GnoForm' +import Col from '~/components/layout/Col' +import Button from '~/components/layout/Button' +import Block from '~/components/layout/Block' +import Hairline from '~/components/layout/Hairline' +import Field from '~/components/forms/Field' +import TextField from '~/components/forms/TextField' +import type { Owner } from '~/routes/safe/store/models/owner' +import { + composeValidators, + required, + minValue, + maxValue, + mustBeInteger, +} from '~/components/forms/validator' +import { styles } from './style' + +type Props = { + onClose: () => void, + classes: Object, + owners: List, + threshold: number, + onClickBack: Function, + onSubmit: Function, +} + +const ThresholdForm = ({ + classes, + onClose, + owners, + threshold, + onClickBack, + onSubmit, +}: Props) => { + const handleSubmit = (values) => { + onSubmit(values) + } + const defaultThreshold = threshold > 1 ? threshold - 1 : threshold + + return ( + + + + Remove owner + + 2 of 3 + + + + + + + {(...args) => { + const formState = args[2] + const numOptions = owners.size > 1 ? owners.size - 1 : 1 + + return ( + + + + + Set the required owner confirmations: + + + + + Any transaction over any daily limit requires the confirmation of: + + + + + ( + + + {[...Array(Number(numOptions))].map((x, index) => ( + + {index + 1} + + ))} + + {props.meta.error && props.meta.touched && ( + + {props.meta.error} + + )} + + )} + validate={composeValidators(required, mustBeInteger, minValue(1), maxValue(numOptions))} + data-testid="threshold-select-input" + /> + + + + out of + {' '} + {owners.size - 1} + {' '} + owner(s) + + + + + + + + + + + ) + }} + + + ) +} + +export default withStyles(styles)(ThresholdForm) diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/style.js b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/style.js new file mode 100644 index 00000000..dfd2de15 --- /dev/null +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/style.js @@ -0,0 +1,45 @@ +// @flow +import { lg, md, sm } from '~/theme/variables' + +export const styles = () => ({ + heading: { + padding: `${sm} ${lg}`, + justifyContent: 'flex-start', + boxSizing: 'border-box', + maxHeight: '75px', + }, + annotation: { + letterSpacing: '-1px', + color: '#a2a8ba', + marginRight: 'auto', + marginLeft: '20px', + }, + manage: { + fontSize: '24px', + }, + closeIcon: { + height: '35px', + width: '35px', + }, + headingText: { + fontSize: '16px', + }, + formContainer: { + padding: `${md} ${lg}`, + minHeight: '340px', + }, + ownersText: { + marginLeft: sm, + }, + buttonRow: { + height: '84px', + justifyContent: 'center', + }, + inputRow: { + position: 'relative', + }, + errorText: { + position: 'absolute', + bottom: '-25px', + }, +}) diff --git a/src/routes/safe/components/Settings/ManageOwners/index.jsx b/src/routes/safe/components/Settings/ManageOwners/index.jsx index 3b07c3b7..bdc38b17 100644 --- a/src/routes/safe/components/Settings/ManageOwners/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/index.jsx @@ -93,7 +93,7 @@ class ManageOwners extends React.Component { showAddOwner, showRemoveOwner, selectedOwnerName, - selectedOwnerAddress + selectedOwnerAddress, } = this.state const columns = generateColumns()