WA-280 - Feature not allowing similar owners accounts (#14)
* WA-280 adding unique address form validator * WA-280 Adding test when two owner addresses are the same form raises an error * WA-280 Refactor - Create safe button, centering loader and improving response on welcome view
This commit is contained in:
parent
f4284d513a
commit
0094abd4ac
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
import { storiesOf } from '@storybook/react'
|
||||||
|
import * as React from 'react'
|
||||||
|
import styles from '~/components/layout/PageFrame/index.scss'
|
||||||
|
import Component from './index'
|
||||||
|
|
||||||
|
const FrameDecorator = story => (
|
||||||
|
<div className={styles.frame}>
|
||||||
|
{ story() }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
storiesOf('Components', module)
|
||||||
|
.addDecorator(FrameDecorator)
|
||||||
|
.add('Loader', () => <Component />)
|
|
@ -1,7 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import Page from '~/components/layout/Page'
|
||||||
import { CircularProgress } from 'material-ui/Progress'
|
import { CircularProgress } from 'material-ui/Progress'
|
||||||
|
|
||||||
const Loader = () => <CircularProgress size={50} />
|
const centerStyle = {
|
||||||
|
margin: 'auto 0',
|
||||||
|
}
|
||||||
|
|
||||||
|
const Loader = () => (
|
||||||
|
<Page align="center">
|
||||||
|
<CircularProgress style={centerStyle} size={60} />
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
|
||||||
export default Loader
|
export default Loader
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Bold from '~/components/layout/Bold'
|
import Bold from '~/components/layout/Bold'
|
||||||
import Button from '~/components/layout/Button'
|
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph/index'
|
import Paragraph from '~/components/layout/Paragraph/index'
|
||||||
import { OPEN_ADDRESS } from '~/routes/routes'
|
import { CreateSafe } from '~/routes/welcome/components/Layout'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
text: string
|
text: string,
|
||||||
|
provider: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const NoSafe = ({ text }: Props) => (
|
const NoSafe = ({ text, provider }: Props) => (
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||||
<Paragraph size="lg">
|
<Paragraph size="lg">
|
||||||
|
@ -20,9 +19,7 @@ const NoSafe = ({ text }: Props) => (
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
|
||||||
<Link to={OPEN_ADDRESS}>
|
<CreateSafe provider={provider} />
|
||||||
<Button variant="raised" size="small" color="primary">CREATE A NEW SAFE</Button>
|
|
||||||
</Link>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { getWeb3 } from '~/wallets/getWeb3'
|
import { getWeb3 } from '~/wallets/getWeb3'
|
||||||
|
|
||||||
type Field = boolean | number | string
|
type Field = boolean | string
|
||||||
|
|
||||||
export const required = (value: Field) => (value ? undefined : 'Required')
|
export const required = (value: Field) => (value ? undefined : 'Required')
|
||||||
|
|
||||||
export const mustBeNumber = (value: number) =>
|
export const mustBeNumber = (value: number) =>
|
||||||
(Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
(Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
||||||
|
|
||||||
export const minValue = (min: number) => (value: number) => {
|
export const minValue = (min: number) => (value: string) => {
|
||||||
if (Number.isNaN(Number(value)) || value >= min) {
|
if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) >= Number(min)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Should be at least ${min}`
|
return `Should be at least ${min}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const maxValue = (max: number) => (value: number) => {
|
export const maxValue = (max: number) => (value: string) => {
|
||||||
if (Number.isNaN(Number(value)) || value <= max) {
|
if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) <= Number(max)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,5 +32,10 @@ export const mustBeEthereumAddress = (address: Field) => {
|
||||||
return isAddress ? undefined : 'Address should be a valid Ethereum address'
|
return isAddress ? undefined : 'Address should be a valid Ethereum address'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
|
||||||
|
|
||||||
|
export const uniqueAddress = (addresses: string[]) => (value: string) =>
|
||||||
|
(addresses.includes(value) ? ADDRESS_REPEATED_ERROR : undefined)
|
||||||
|
|
||||||
export const composeValidators = (...validators: Function[]) => (value: Field) =>
|
export const composeValidators = (...validators: Function[]) => (value: Field) =>
|
||||||
validators.reduce((error, validator) => error || validator(value), undefined)
|
validators.reduce((error, validator) => error || validator(value), undefined)
|
||||||
|
|
|
@ -11,14 +11,16 @@ type Props = {
|
||||||
padding?: 'xs' | 'sm' | 'md',
|
padding?: 'xs' | 'sm' | 'md',
|
||||||
to: string,
|
to: string,
|
||||||
children: React$Node,
|
children: React$Node,
|
||||||
|
color?: 'regular' | 'white',
|
||||||
className?: string,
|
className?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GnosisLink = ({
|
const GnosisLink = ({
|
||||||
to, children, className, padding, ...props
|
to, children, color, className, padding, ...props
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const classes = cx(
|
const classes = cx(
|
||||||
styles.link,
|
styles.link,
|
||||||
|
color || 'regular',
|
||||||
padding ? capitalize(padding, 'padding') : undefined,
|
padding ? capitalize(padding, 'padding') : undefined,
|
||||||
className,
|
className,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
.link {
|
.link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regular {
|
||||||
color: $secondary;
|
color: $secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.paddingXs {
|
.paddingXs {
|
||||||
padding-right: $xs;
|
padding-right: $xs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,34 +8,41 @@ import { getProviderInfo } from '~/wallets/getWeb3'
|
||||||
import Wrapper from '~/test/Wrapper'
|
import Wrapper from '~/test/Wrapper'
|
||||||
import { CONFIRMATIONS_ERROR } from '~/routes/open/components/SafeForm'
|
import { CONFIRMATIONS_ERROR } from '~/routes/open/components/SafeForm'
|
||||||
|
|
||||||
|
const obSubmitMock = () => {}
|
||||||
|
|
||||||
describe('React DOM TESTS > Create Safe form', () => {
|
describe('React DOM TESTS > Create Safe form', () => {
|
||||||
|
let open
|
||||||
|
let fieldOwners
|
||||||
|
let fieldConfirmations
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// init app web3 instance
|
// init app web3 instance
|
||||||
await getProviderInfo()
|
await getProviderInfo()
|
||||||
})
|
|
||||||
|
|
||||||
it('should not allow to continue if confirmations are higher than owners', async () => {
|
open = TestUtils.renderIntoDocument((
|
||||||
// GIVEN
|
|
||||||
const open = TestUtils.renderIntoDocument((
|
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Layout
|
<Layout
|
||||||
provider="METAMASK"
|
provider="METAMASK"
|
||||||
userAccount="foo"
|
userAccount="foo"
|
||||||
safeAddress=""
|
safeAddress=""
|
||||||
safeTx=""
|
safeTx=""
|
||||||
onCallSafeContractSubmit={() => { }}
|
onCallSafeContractSubmit={obSubmitMock}
|
||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
))
|
))
|
||||||
|
|
||||||
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(open, 'input')
|
const inputs = TestUtils.scryRenderedDOMComponentsWithTag(open, 'input')
|
||||||
|
const indexOwners = 1
|
||||||
|
const indexConfirmations = 2
|
||||||
|
fieldOwners = inputs[indexOwners]
|
||||||
|
fieldConfirmations = inputs[indexConfirmations]
|
||||||
|
|
||||||
const fieldOwners = inputs[1]
|
|
||||||
expect(fieldOwners.name).toEqual(FIELD_OWNERS)
|
expect(fieldOwners.name).toEqual(FIELD_OWNERS)
|
||||||
TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } })
|
|
||||||
|
|
||||||
const fieldConfirmations = inputs[2]
|
|
||||||
expect(fieldConfirmations.name).toEqual(FIELD_CONFIRMATIONS)
|
expect(fieldConfirmations.name).toEqual(FIELD_CONFIRMATIONS)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow to continue if confirmations are higher than owners', async () => {
|
||||||
|
// GIVEN
|
||||||
|
TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } })
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
TestUtils.Simulate.change(fieldConfirmations, { target: { value: '2' } })
|
TestUtils.Simulate.change(fieldConfirmations, { target: { value: '2' } })
|
||||||
|
@ -48,4 +55,19 @@ describe('React DOM TESTS > Create Safe form', () => {
|
||||||
expect(confirmationsField.props.meta.valid).toBe(false)
|
expect(confirmationsField.props.meta.valid).toBe(false)
|
||||||
expect(confirmationsField.props.meta.error).toBe(CONFIRMATIONS_ERROR)
|
expect(confirmationsField.props.meta.error).toBe(CONFIRMATIONS_ERROR)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should raise error when confirmations are 012 and number of owners are 2', async () => {
|
||||||
|
// GIVEN
|
||||||
|
TestUtils.Simulate.change(fieldOwners, { target: { value: '2' } })
|
||||||
|
// WHEN
|
||||||
|
TestUtils.Simulate.change(fieldConfirmations, { target: { value: '014' } })
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const muiFields = TestUtils.scryRenderedComponentsWithType(open, TextField)
|
||||||
|
expect(7).toEqual(muiFields.length)
|
||||||
|
const confirmationsField = muiFields[6]
|
||||||
|
|
||||||
|
expect(confirmationsField.props.meta.valid).toBe(false)
|
||||||
|
expect(confirmationsField.props.meta.error).toBe(CONFIRMATIONS_ERROR)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,15 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import { composeValidators, minValue, maxValue, mustBeNumber, mustBeEthereumAddress, required } from '~/components/forms/validator'
|
import {
|
||||||
|
composeValidators,
|
||||||
|
minValue,
|
||||||
|
maxValue,
|
||||||
|
mustBeNumber,
|
||||||
|
mustBeEthereumAddress,
|
||||||
|
required,
|
||||||
|
uniqueAddress,
|
||||||
|
} from '~/components/forms/validator'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Heading from '~/components/layout/Heading'
|
import Heading from '~/components/layout/Heading'
|
||||||
|
@ -12,12 +20,20 @@ import { FIELD_OWNERS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/c
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
numOwners: number,
|
numOwners: number,
|
||||||
|
otherAccounts: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_NUMBER_OWNERS = 50
|
const MAX_NUMBER_OWNERS = 50
|
||||||
|
|
||||||
|
const getAddressValidators = (addresses: string[], position: number) => {
|
||||||
|
const copy = addresses.slice()
|
||||||
|
copy.splice(position, 1)
|
||||||
|
|
||||||
|
return composeValidators(required, mustBeEthereumAddress, uniqueAddress(copy))
|
||||||
|
}
|
||||||
|
|
||||||
const Owners = (props: Props) => {
|
const Owners = (props: Props) => {
|
||||||
const { numOwners } = props
|
const { numOwners, otherAccounts } = props
|
||||||
const validNumber = numOwners && Number.isInteger(Number(numOwners))
|
const validNumber = numOwners && Number.isInteger(Number(numOwners))
|
||||||
const renderOwners = validNumber && Number(numOwners) <= MAX_NUMBER_OWNERS
|
const renderOwners = validNumber && Number(numOwners) <= MAX_NUMBER_OWNERS
|
||||||
|
|
||||||
|
@ -54,7 +70,7 @@ const Owners = (props: Props) => {
|
||||||
name={getOwnerAddressBy(index)}
|
name={getOwnerAddressBy(index)}
|
||||||
component={TextField}
|
component={TextField}
|
||||||
type="text"
|
type="text"
|
||||||
validate={composeValidators(required, mustBeEthereumAddress)}
|
validate={getAddressValidators(otherAccounts, index)}
|
||||||
placeholder="Owner Address*"
|
placeholder="Owner Address*"
|
||||||
text="Owner Address"
|
text="Owner Address"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
import TextField from '~/components/forms/TextField'
|
||||||
|
import * as React from 'react'
|
||||||
|
import * as TestUtils from 'react-dom/test-utils'
|
||||||
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
|
import { FIELD_OWNERS } from '~/routes/open/components/fields'
|
||||||
|
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
|
import { getProviderInfo } from '~/wallets/getWeb3'
|
||||||
|
import Wrapper from '~/test/Wrapper'
|
||||||
|
import { ADDRESS_REPEATED_ERROR } from '~/components/forms/validator'
|
||||||
|
import Owners from './index'
|
||||||
|
|
||||||
|
const onSubmitMock = () => {}
|
||||||
|
const childrenMock = () => {}
|
||||||
|
|
||||||
|
describe('React DOM TESTS > Create Safe form', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// init app web3 instance
|
||||||
|
await getProviderInfo()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow to continue if owners addresses are duplicated', async () => {
|
||||||
|
// GIVEN
|
||||||
|
const open = TestUtils.renderIntoDocument((
|
||||||
|
<Wrapper>
|
||||||
|
<GnoForm
|
||||||
|
onSubmit={onSubmitMock}
|
||||||
|
padding={15}
|
||||||
|
render={({ values }) => (
|
||||||
|
<Owners
|
||||||
|
numOwners={values.owners}
|
||||||
|
otherAccounts={getAccountsFrom(values)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{childrenMock}
|
||||||
|
</GnoForm>
|
||||||
|
</Wrapper>
|
||||||
|
))
|
||||||
|
|
||||||
|
let inputs = TestUtils.scryRenderedDOMComponentsWithTag(open, 'input')
|
||||||
|
const fieldOwners = inputs[0]
|
||||||
|
expect(fieldOwners.name).toEqual(FIELD_OWNERS)
|
||||||
|
TestUtils.Simulate.change(fieldOwners, { target: { value: '2' } })
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
inputs = TestUtils.scryRenderedDOMComponentsWithTag(open, 'input')
|
||||||
|
const firstOwnerAddress = inputs[2]
|
||||||
|
TestUtils.Simulate.change(firstOwnerAddress, { target: { value: '0xC21aC257Db500a87c65Daa980432F216A719bA30' } })
|
||||||
|
const secondOwnerAddress = inputs[4]
|
||||||
|
TestUtils.Simulate.change(secondOwnerAddress, { target: { value: '0xC21aC257Db500a87c65Daa980432F216A719bA30' } })
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const muiFields = TestUtils.scryRenderedComponentsWithType(open, TextField)
|
||||||
|
expect(5).toEqual(muiFields.length)
|
||||||
|
const secondAddressField = muiFields[4]
|
||||||
|
|
||||||
|
expect(secondAddressField.props.meta.valid).toBe(false)
|
||||||
|
expect(secondAddressField.props.meta.error).toBe(ADDRESS_REPEATED_ERROR)
|
||||||
|
})
|
||||||
|
})
|
|
@ -2,6 +2,7 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
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 { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
import Name from './Name'
|
import Name from './Name'
|
||||||
import Owners from './Owners'
|
import Owners from './Owners'
|
||||||
import Confirmations from './Confirmations'
|
import Confirmations from './Confirmations'
|
||||||
|
@ -11,7 +12,7 @@ export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher th
|
||||||
export const safeFieldsValidation = (values: Object) => {
|
export const safeFieldsValidation = (values: Object) => {
|
||||||
const errors = {}
|
const errors = {}
|
||||||
|
|
||||||
if (values.owners < values.confirmations) {
|
if (Number.parseInt(values.owners, 10) < Number.parseInt(values.confirmations, 10)) {
|
||||||
errors.confirmations = CONFIRMATIONS_ERROR
|
errors.confirmations = CONFIRMATIONS_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ export default () => ({ values }: Object) => (
|
||||||
<Block margin="md">
|
<Block margin="md">
|
||||||
<Heading tag="h2" margin="lg">Deploy a new Safe</Heading>
|
<Heading tag="h2" margin="lg">Deploy a new Safe</Heading>
|
||||||
<Name />
|
<Name />
|
||||||
<Owners numOwners={values.owners} />
|
<Owners numOwners={values.owners} otherAccounts={getAccountsFrom(values)} />
|
||||||
<Confirmations />
|
<Confirmations />
|
||||||
</Block>
|
</Block>
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,11 +6,11 @@ import GnoSafe from './Safe'
|
||||||
|
|
||||||
type Props = SelectorProps
|
type Props = SelectorProps
|
||||||
|
|
||||||
const Layout = ({ safe }: Props) => (
|
const Layout = ({ safe, provider }: Props) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{ safe
|
{ safe
|
||||||
? <GnoSafe safe={safe} />
|
? <GnoSafe safe={safe} />
|
||||||
: <NoSafe text="Not found safe" />
|
: <NoSafe provider={provider} text="Not found safe" />
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,11 +14,12 @@ const FrameDecorator = story => (
|
||||||
|
|
||||||
storiesOf('Routes /safe:address', module)
|
storiesOf('Routes /safe:address', module)
|
||||||
.addDecorator(FrameDecorator)
|
.addDecorator(FrameDecorator)
|
||||||
.add('Safe undefined', () => <Component safe={undefined} />)
|
.add('Safe undefined being connected', () => <Component safe={undefined} provider="METAMASK" />)
|
||||||
|
.add('Safe undefined NOT connected', () => <Component safe={undefined} provider="" />)
|
||||||
.add('Safe with 2 owners', () => {
|
.add('Safe with 2 owners', () => {
|
||||||
const safe = SafeFactory.twoOwnersSafe
|
const safe = SafeFactory.twoOwnersSafe
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component safe={safe} />
|
<Component safe={safe} provider="METAMASK" />
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,11 +9,11 @@ type Props = SelectorProps
|
||||||
|
|
||||||
class SafeView extends React.PureComponent<Props> {
|
class SafeView extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { safe } = this.props
|
const { safe, provider } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Layout safe={safe} />
|
<Layout provider={provider} safe={safe} />
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { createStructuredSelector } from 'reselect'
|
import { createStructuredSelector } from 'reselect'
|
||||||
import { safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
import { safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
||||||
|
import { providerNameSelector } from '~/wallets/store/selectors/index'
|
||||||
|
|
||||||
export type SelectorProps = {
|
export type SelectorProps = {
|
||||||
safe: SafeSelectorProps,
|
safe: SafeSelectorProps,
|
||||||
|
provider: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createStructuredSelector({
|
export default createStructuredSelector({
|
||||||
safe: safeSelector,
|
safe: safeSelector,
|
||||||
|
provider: providerNameSelector,
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,17 +6,18 @@ import { type Safe } from '~/routes/safe/store/model/safe'
|
||||||
import SafeTable from '~/routes/safeList/components/SafeTable'
|
import SafeTable from '~/routes/safeList/components/SafeTable'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
safes: List<Safe>
|
safes: List<Safe>,
|
||||||
|
provider: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SafeList = ({ safes }: Props) => {
|
const SafeList = ({ safes, provider }: Props) => {
|
||||||
const safesAvailable = safes && safes.count() > 0
|
const safesAvailable = safes && safes.count() > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{ safesAvailable
|
{ safesAvailable
|
||||||
? <SafeTable safes={safes} />
|
? <SafeTable safes={safes} />
|
||||||
: <NoSafe text="No safes created, please create a new one" />
|
: <NoSafe provider={provider} text="No safes created, please create a new one" />
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,10 +15,15 @@ const FrameDecorator = story => (
|
||||||
|
|
||||||
storiesOf('Routes /safes', module)
|
storiesOf('Routes /safes', module)
|
||||||
.addDecorator(FrameDecorator)
|
.addDecorator(FrameDecorator)
|
||||||
.add('Safe List whithout safes', () => <Component safes={List([])} />)
|
.add('Safe List whithout safes and connected', () => (
|
||||||
|
<Component provider="METAMASK" safes={List([])} />
|
||||||
|
))
|
||||||
|
.add('Safe List whithout safes and NOT connected', () => (
|
||||||
|
<Component provider="" safes={List([])} />
|
||||||
|
))
|
||||||
.add('Safe List whith 2 safes', () => {
|
.add('Safe List whith 2 safes', () => {
|
||||||
const safes = List([SafeFactory.oneOwnerSafe, SafeFactory.twoOwnersSafe])
|
const safes = List([SafeFactory.oneOwnerSafe, SafeFactory.twoOwnersSafe])
|
||||||
return (
|
return (
|
||||||
<Component safes={safes} />
|
<Component provider="METAMASK" safes={safes} />
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,12 +8,13 @@ import Layout from '../components/Layout'
|
||||||
import selector from './selector'
|
import selector from './selector'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
safes: List<Safe>
|
safes: List<Safe>,
|
||||||
|
provider: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SafeList = ({ safes }: Props) => (
|
const SafeList = ({ safes, provider }: Props) => (
|
||||||
<Page>
|
<Page>
|
||||||
<Layout safes={safes} />
|
<Layout safes={safes} provider={provider} />
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { createStructuredSelector } from 'reselect'
|
import { createStructuredSelector } from 'reselect'
|
||||||
import { safesListSelector } from '~/routes/safeList/store/selectors'
|
import { safesListSelector } from '~/routes/safeList/store/selectors'
|
||||||
|
import { providerNameSelector } from '~/wallets/store/selectors/index'
|
||||||
|
|
||||||
export default createStructuredSelector({
|
export default createStructuredSelector({
|
||||||
safes: safesListSelector,
|
safes: safesListSelector,
|
||||||
|
provider: providerNameSelector,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import * as React from 'react'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Link from '~/components/layout/Link'
|
import Link from '~/components/layout/Link'
|
||||||
import { OPEN_ADDRESS, SAFELIST_ADDRESS } from '~/routes/routes'
|
import { OPEN_ADDRESS } from '~/routes/routes'
|
||||||
import styles from './Layout.scss'
|
import styles from './Layout.scss'
|
||||||
|
|
||||||
const vault = require('../assets/vault.svg')
|
const vault = require('../assets/vault.svg')
|
||||||
|
@ -14,27 +14,27 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SafeProps = {
|
type SafeProps = {
|
||||||
|
provider: string,
|
||||||
size?: 'small' | 'medium',
|
size?: 'small' | 'medium',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateSafe = ({ size }: SafeProps) => (
|
export const CreateSafe = ({ size, provider }: SafeProps) => (
|
||||||
<Link to={OPEN_ADDRESS}>
|
<Button
|
||||||
<Button variant="raised" size={size || 'medium'} color="primary">
|
variant="raised"
|
||||||
Create a new Safe
|
size={size || 'medium'}
|
||||||
|
color="primary"
|
||||||
|
disabled={!provider}
|
||||||
|
>
|
||||||
|
<Link to={OPEN_ADDRESS} color="white">Create a new Safe</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const Welcome = ({ provider }: Props) => (
|
const Welcome = ({ provider }: Props) => (
|
||||||
<Block className={styles.safe}>
|
<Block className={styles.safe}>
|
||||||
<Img alt="Safe Box" src={vault} height={330} />
|
<Img alt="Safe Box" src={vault} height={330} />
|
||||||
<Block className={styles.safeActions} margin="md">
|
<Block className={styles.safeActions} margin="md">
|
||||||
{ provider && <CreateSafe /> }
|
<CreateSafe provider={provider} />
|
||||||
<Link to={SAFELIST_ADDRESS}>
|
|
||||||
<Button variant="raised" color="primary">
|
|
||||||
See Safe list
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue