diff --git a/src/components/forms/validator.js b/src/components/forms/validator.js index f807840c..8637f158 100644 --- a/src/components/forms/validator.js +++ b/src/components/forms/validator.js @@ -8,6 +8,14 @@ export const required = (value: Field) => (value ? undefined : 'Required') export const mustBeNumber = (value: number) => (Number.isNaN(Number(value)) ? 'Must be a number' : undefined) +export const greaterThan = (min: number) => (value: string) => { + if (Number.isNaN(Number(value)) || Number.parseFloat(value) > Number(min)) { + return undefined + } + + return `Should be greater than ${min}` +} + export const minValue = (min: number) => (value: string) => { if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) >= Number(min)) { return undefined diff --git a/src/routes/open/components/Layout.test.js b/src/routes/open/components/Layout.test.js index 4193d974..db2eaf1f 100644 --- a/src/routes/open/components/Layout.test.js +++ b/src/routes/open/components/Layout.test.js @@ -1,7 +1,14 @@ // @flow import TestUtils from 'react-dom/test-utils' import { store } from '~/store' -import { FIELD_NAME, FIELD_OWNERS, FIELD_CONFIRMATIONS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields' +import { + FIELD_NAME, + FIELD_OWNERS, + FIELD_CONFIRMATIONS, + FIELD_DAILY_LIMIT, + getOwnerNameBy, + getOwnerAddressBy, +} from '~/routes/open/components/fields' import { DEPLOYED_COMPONENT_ID } from '~/routes/open/components/FormConfirmation' import { sleep } from '~/utils/timer' import { getProviderInfo } from '~/wallets/getWeb3' @@ -26,6 +33,9 @@ describe('React DOM TESTS > Create Safe form', () => { const fieldConfirmations = inputs[2] expect(fieldConfirmations.name).toEqual(FIELD_CONFIRMATIONS) + const dailyLimitConfirmations = inputs[3] + expect(dailyLimitConfirmations.name).toEqual(FIELD_DAILY_LIMIT) + TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } }) const inputsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(open, 'input') @@ -39,6 +49,7 @@ describe('React DOM TESTS > Create Safe form', () => { TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } }) TestUtils.Simulate.change(fieldConfirmations, { target: { value: '1' } }) TestUtils.Simulate.change(ownerName, { target: { value: 'Adolfo Eth Account' } }) + TestUtils.Simulate.change(dailyLimitConfirmations, { target: { value: '10' } }) const form = TestUtils.findRenderedDOMComponentWithTag(open, 'form') // One submit per step when creating a safe diff --git a/src/routes/open/components/ReviewInformation/index.jsx b/src/routes/open/components/ReviewInformation/index.jsx index 8b2b286a..2909aa8c 100644 --- a/src/routes/open/components/ReviewInformation/index.jsx +++ b/src/routes/open/components/ReviewInformation/index.jsx @@ -7,6 +7,7 @@ import Col from '~/components/layout/Col' import Heading from '~/components/layout/Heading' import Row from '~/components/layout/Row' import Paragraph from '~/components/layout/Paragraph' +import { FIELD_NAME, FIELD_CONFIRMATIONS, FIELD_DAILY_LIMIT } from '../fields' type FormProps = { values: Object, @@ -20,10 +21,13 @@ const ReviewInformation = () => ({ values }: FormProps) => { Review the Safe information - Safe Name: {values.name} + Safe Name: {values[FIELD_NAME]} - Required confirmations: {values.confirmations} + Required confirmations: {values[FIELD_CONFIRMATIONS]} + + + Daily limit: {values[FIELD_DAILY_LIMIT]} ETH Owners { names.map((name, index) => ( diff --git a/src/routes/open/components/SafeForm/Confirmations/index.test.js b/src/routes/open/components/SafeForm/Confirmations/index.test.js index a86ae028..2c95c3c5 100644 --- a/src/routes/open/components/SafeForm/Confirmations/index.test.js +++ b/src/routes/open/components/SafeForm/Confirmations/index.test.js @@ -49,7 +49,7 @@ describe('React DOM TESTS > Create Safe form', () => { // THEN const muiFields = TestUtils.scryRenderedComponentsWithType(open, TextField) - expect(5).toEqual(muiFields.length) + expect(6).toEqual(muiFields.length) const confirmationsField = muiFields[4] expect(confirmationsField.props.meta.valid).toBe(false) @@ -64,7 +64,7 @@ describe('React DOM TESTS > Create Safe form', () => { // THEN const muiFields = TestUtils.scryRenderedComponentsWithType(open, TextField) - expect(7).toEqual(muiFields.length) + expect(8).toEqual(muiFields.length) const confirmationsField = muiFields[6] expect(confirmationsField.props.meta.valid).toBe(false) diff --git a/src/routes/open/components/SafeForm/DailyLimit/index.jsx b/src/routes/open/components/SafeForm/DailyLimit/index.jsx new file mode 100644 index 00000000..83c4ff6b --- /dev/null +++ b/src/routes/open/components/SafeForm/DailyLimit/index.jsx @@ -0,0 +1,22 @@ +// @flow +import * as React from 'react' +import Field from '~/components/forms/Field' +import TextField from '~/components/forms/TextField' +import { composeValidators, mustBeNumber, required, greaterThan } from '~/components/forms/validator' +import Block from '~/components/layout/Block' +import { FIELD_DAILY_LIMIT } from '~/routes/open/components/fields' + +const DailyLimit = () => ( + + + +) + +export default DailyLimit diff --git a/src/routes/open/components/SafeForm/index.jsx b/src/routes/open/components/SafeForm/index.jsx index 478e2e83..b79d00a2 100644 --- a/src/routes/open/components/SafeForm/index.jsx +++ b/src/routes/open/components/SafeForm/index.jsx @@ -6,6 +6,7 @@ import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor' import Name from './Name' import Owners from './Owners' import Confirmations from './Confirmations' +import DailyLimit from './DailyLimit' export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners' @@ -25,5 +26,6 @@ export default () => ({ values }: Object) => ( + ) diff --git a/src/routes/open/components/fields.js b/src/routes/open/components/fields.js index b7e37ac9..6755b839 100644 --- a/src/routes/open/components/fields.js +++ b/src/routes/open/components/fields.js @@ -2,6 +2,7 @@ export const FIELD_NAME: string = 'name' export const FIELD_CONFIRMATIONS: string = 'confirmations' export const FIELD_OWNERS: string = 'owners' +export const FIELD_DAILY_LIMIT: string = 'limit' export const getOwnerNameBy = (index: number) => `owner${index}Name` export const getOwnerAddressBy = (index: number) => `owner${index}Address` diff --git a/src/routes/open/container/Open.jsx b/src/routes/open/container/Open.jsx index c80aadf4..e3897ef7 100644 --- a/src/routes/open/container/Open.jsx +++ b/src/routes/open/container/Open.jsx @@ -3,7 +3,7 @@ import * as React from 'react' import { connect } from 'react-redux' import contract from 'truffle-contract' import Page from '~/components/layout/Page' -import { getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom } from '~/routes/open/utils/safeDataExtractor' +import { getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom, getDailyLimitFrom } from '~/routes/open/utils/safeDataExtractor' import { getWeb3 } from '~/wallets/getWeb3' import { promisify } from '~/utils/promisify' import Safe from '#/GnosisSafe.json' @@ -26,12 +26,13 @@ const createSafe = async (safeContract, values, userAccount, addSafe) => { const numConfirmations = getThresholdFrom(values) const name = getSafeNameFrom(values) const owners = getNamesFrom(values) + const dailyLimit = getDailyLimitFrom(values) const web3 = getWeb3() safeContract.setProvider(web3.currentProvider) const safe = await safeContract.new(accounts, numConfirmations, 0, 0, { from: userAccount, gas: '5000000' }) - addSafe(name, safe.address, numConfirmations, owners, accounts) + addSafe(name, safe.address, numConfirmations, dailyLimit, owners, accounts) return safe } diff --git a/src/routes/open/utils/safeDataExtractor.js b/src/routes/open/utils/safeDataExtractor.js index e0d84130..25a91fc5 100644 --- a/src/routes/open/utils/safeDataExtractor.js +++ b/src/routes/open/utils/safeDataExtractor.js @@ -1,4 +1,6 @@ // @flow +export const getDailyLimitFrom = (values: Object): number => Number(values.limit) + export const getAccountsFrom = (values: Object): string[] => { const accounts = Object.keys(values).sort().filter(key => /^owner\d+Address$/.test(key)) diff --git a/src/routes/safe/component/Safe/DailyLimit.jsx b/src/routes/safe/component/Safe/DailyLimit.jsx new file mode 100644 index 00000000..20bd47c7 --- /dev/null +++ b/src/routes/safe/component/Safe/DailyLimit.jsx @@ -0,0 +1,21 @@ +// @flow +import * as React from 'react' +import { ListItem } from 'material-ui/List' +import Avatar from 'material-ui/Avatar' +import NotificationsPaused from 'material-ui-icons/NotificationsPaused' +import ListItemText from '~/components/List/ListItemText' + +type Props = { + limit: number, +} + +const DailyLimit = ({ limit }: Props) => ( + + + + + + +) + +export default DailyLimit diff --git a/src/routes/safe/component/Safe/Owners.jsx b/src/routes/safe/component/Safe/Owners.jsx index e552beb1..e4801fb0 100644 --- a/src/routes/safe/component/Safe/Owners.jsx +++ b/src/routes/safe/component/Safe/Owners.jsx @@ -39,7 +39,7 @@ const Owners = openHoc(({ {owners.map(owner => ( - + diff --git a/src/routes/safe/component/Safe/index.jsx b/src/routes/safe/component/Safe/index.jsx index 40a1c9c6..442373e5 100644 --- a/src/routes/safe/component/Safe/index.jsx +++ b/src/routes/safe/component/Safe/index.jsx @@ -12,6 +12,7 @@ import Address from './Address' import Balance from './Balance' import Owners from './Owners' import Confirmations from './Confirmations' +import DailyLimit from './DailyLimit' type SafeProps = { safe: Safe, @@ -20,6 +21,7 @@ type SafeProps = { const listStyle = { width: '100%', + minWidth: '485px', } class GnoSafe extends React.PureComponent { @@ -34,6 +36,7 @@ class GnoSafe extends React.PureComponent {
+ diff --git a/src/routes/safe/store/actions/addSafe.js b/src/routes/safe/store/actions/addSafe.js index 8a7a924f..e4a1801e 100644 --- a/src/routes/safe/store/actions/addSafe.js +++ b/src/routes/safe/store/actions/addSafe.js @@ -15,13 +15,14 @@ export const buildOwnersFrom = (names: string[], addresses: string[]) => { const addSafe = createAction( ADD_SAFE, ( - name: string, address: string, confirmations: number, + name: string, address: string, + confirmations: number, dailyLimit: number, ownersName: string[], ownersAddress: string[], ): SafeProps => { const owners: List = buildOwnersFrom(ownersName, ownersAddress) return ({ - address, name, confirmations, owners, + address, name, confirmations, owners, dailyLimit, }) }, ) diff --git a/src/routes/safe/store/model/safe.js b/src/routes/safe/store/model/safe.js index 146085a0..28e25209 100644 --- a/src/routes/safe/store/model/safe.js +++ b/src/routes/safe/store/model/safe.js @@ -8,6 +8,7 @@ export type SafeProps = { address: string, confirmations: number, owners: List, + dailyLimit: number, } export const makeSafe: RecordFactory = Record({ @@ -15,6 +16,7 @@ export const makeSafe: RecordFactory = Record({ address: '', confirmations: 0, owners: List([]), + dailyLimit: 0, }) export type Safe = RecordOf diff --git a/src/routes/safe/store/test/builder/deployedSafe.builder.js b/src/routes/safe/store/test/builder/deployedSafe.builder.js index 34bb71b0..6c9b9dca 100644 --- a/src/routes/safe/store/test/builder/deployedSafe.builder.js +++ b/src/routes/safe/store/test/builder/deployedSafe.builder.js @@ -33,6 +33,7 @@ const deploySafe = async (safe: React$Component<{}>) => { const fieldName = inputs[0] const fieldOwners = inputs[1] const fieldConfirmations = inputs[2] + const fieldDailyLimit = inputs[3] TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } }) const inputsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') @@ -41,6 +42,7 @@ const deploySafe = async (safe: React$Component<{}>) => { TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } }) TestUtils.Simulate.change(fieldConfirmations, { target: { value: '1' } }) TestUtils.Simulate.change(ownerName, { target: { value: 'Adolfo Eth Account' } }) + TestUtils.Simulate.change(fieldDailyLimit, { target: { value: '10' } }) const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form') diff --git a/src/routes/safe/store/test/builder/safe.builder.js b/src/routes/safe/store/test/builder/safe.builder.js index d7ad86bd..0bde9e05 100644 --- a/src/routes/safe/store/test/builder/safe.builder.js +++ b/src/routes/safe/store/test/builder/safe.builder.js @@ -24,6 +24,11 @@ class SafeBuilder { return this } + withDailyLimit(limit: number) { + this.safe = this.safe.set('dailyLimit', limit) + return this + } + withOwner(names: string[], adresses: string[]) { const owners = buildOwnersFrom(names, adresses) this.safe = this.safe.set('owners', owners) @@ -42,6 +47,7 @@ export class SafeFactory { .withAddress('0x03db1a8b26d08df23337e9276a36b474510f0025') .withName('Adol ICO Safe') .withConfirmations(1) + .withDailyLimit(10) .withOwner(['Adol Metamask'], ['0x03db1a8b26d08df23337e9276a36b474510f0023']) .get() diff --git a/src/routes/safe/store/test/safe.reducer.js b/src/routes/safe/store/test/safe.reducer.js index c9c241df..dea0bfbd 100644 --- a/src/routes/safe/store/test/safe.reducer.js +++ b/src/routes/safe/store/test/safe.reducer.js @@ -23,27 +23,31 @@ const aStore = (initState) => { const providerReducerTests = () => { describe('Safe Actions[addSafe]', () => { let store + let address + let formValues beforeEach(() => { store = aStore() - }) - - it('reducer should return SafeRecord from form values', () => { - // GIVEN - const address = '0x03db1a8b26d08df23337e9276a36b474510f0025' - const formValues = { + address = '0x03db1a8b26d08df23337e9276a36b474510f0025' + formValues = { [SafeFields.FIELD_NAME]: 'Adol ICO Safe', [SafeFields.FIELD_CONFIRMATIONS]: 1, [SafeFields.FIELD_OWNERS]: 1, + [SafeFields.FIELD_DAILY_LIMIT]: 10, [SafeFields.getOwnerAddressBy(0)]: '0x03db1a8b26d08df23337e9276a36b474510f0023', [SafeFields.getOwnerNameBy(0)]: 'Adol Metamask', address, } + }) + + it('reducer should return SafeRecord from form values', () => { + // GIVEN in beforeEach method // WHEN store.dispatch(addSafe( formValues[SafeFields.FIELD_NAME], formValues.address, formValues[SafeFields.FIELD_CONFIRMATIONS], + formValues[SafeFields.FIELD_DAILY_LIMIT], getNamesFrom(formValues), getAccountsFrom(formValues), )) @@ -54,22 +58,14 @@ const providerReducerTests = () => { }) it('reducer loads information from localStorage', async () => { - // GIVEN - const address = '0x03db1a8b26d08df23337e9276a36b474510f0025' - const formValues = { - [SafeFields.FIELD_NAME]: 'Adol ICO Safe', - [SafeFields.FIELD_CONFIRMATIONS]: 1, - [SafeFields.FIELD_OWNERS]: 1, - [SafeFields.getOwnerAddressBy(0)]: '0x03db1a8b26d08df23337e9276a36b474510f0023', - [SafeFields.getOwnerNameBy(0)]: 'Adol Metamask', - address, - } + // GIVEN in beforeEach method // WHEN store.dispatch(addSafe( formValues[SafeFields.FIELD_NAME], formValues.address, formValues[SafeFields.FIELD_CONFIRMATIONS], + formValues[SafeFields.FIELD_DAILY_LIMIT], getNamesFrom(formValues), getAccountsFrom(formValues), )) diff --git a/src/routes/safeList/components/SafeTable.jsx b/src/routes/safeList/components/SafeTable.jsx index 9b1c97d8..ed7e825c 100644 --- a/src/routes/safeList/components/SafeTable.jsx +++ b/src/routes/safeList/components/SafeTable.jsx @@ -19,6 +19,7 @@ const SafeTable = ({ safes }: Props) => ( Deployed Address Confirmations Number of owners + Daily Limit @@ -29,10 +30,11 @@ const SafeTable = ({ safes }: Props) => ( - {safe.name} - {safe.address} - {safe.confirmations} - {safe.owners.count()} + {safe.get('name')} + {safe.get('address')} + {safe.get('confirmations')} + {safe.get('owners').count()} + {`${safe.get('dailyLimit')} ETH`} ))}