Merge pull request #119 from gnosis/edit-remove-safe

Safe settings: Change Safe name & Remove Safe
This commit is contained in:
Mikhail Mikheev 2019-06-26 15:57:46 +03:00 committed by GitHub
commit 3201cce218
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 920 additions and 19605 deletions

View File

@ -32,13 +32,13 @@
"dependencies": { "dependencies": {
"@gnosis.pm/safe-contracts": "^1.0.0", "@gnosis.pm/safe-contracts": "^1.0.0",
"@gnosis.pm/util-contracts": "2.0.1", "@gnosis.pm/util-contracts": "2.0.1",
"@material-ui/core": "4.1.1", "@material-ui/core": "4.1.3",
"@material-ui/icons": "4.2.0", "@material-ui/icons": "4.2.1",
"@welldone-software/why-did-you-render": "3.2.1", "@welldone-software/why-did-you-render": "3.2.1",
"axios": "0.19.0", "axios": "0.19.0",
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"connected-react-router": "^6.3.1", "connected-react-router": "^6.3.1",
"final-form": "4.15.0", "final-form": "4.16.1",
"history": "^4.7.2", "history": "^4.7.2",
"immortal-db": "^1.0.2", "immortal-db": "^1.0.2",
"immutable": "^4.0.0-rc.9", "immutable": "^4.0.0-rc.9",
@ -47,7 +47,7 @@
"qrcode.react": "^0.9.3", "qrcode.react": "^0.9.3",
"react": "^16.8.6", "react": "^16.8.6",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-final-form": "6.2.1", "react-final-form": "6.3.0",
"react-final-form-listeners": "^1.0.2", "react-final-form-listeners": "^1.0.2",
"react-hot-loader": "4.11.1", "react-hot-loader": "4.11.1",
"react-infinite-scroll-component": "^4.5.2", "react-infinite-scroll-component": "^4.5.2",
@ -86,10 +86,11 @@
"@babel/preset-flow": "^7.0.0-beta.40", "@babel/preset-flow": "^7.0.0-beta.40",
"@babel/preset-react": "^7.0.0-beta.40", "@babel/preset-react": "^7.0.0-beta.40",
"@sambego/storybook-state": "^1.0.7", "@sambego/storybook-state": "^1.0.7",
"@storybook/addon-actions": "5.1.8", "@storybook/addon-actions": "5.1.9",
"@storybook/addon-knobs": "5.1.8", "@storybook/addon-knobs": "5.1.9",
"@storybook/addon-links": "5.1.8", "@storybook/addon-links": "5.1.9",
"@storybook/react": "5.1.8", "@storybook/react": "5.1.9",
"@testing-library/react": "^8.0.1",
"autoprefixer": "9.6.0", "autoprefixer": "9.6.0",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.0.2", "babel-eslint": "10.0.2",
@ -103,15 +104,15 @@
"detect-port": "^1.2.2", "detect-port": "^1.2.2",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0", "eslint-config-airbnb": "^17.1.0",
"eslint-plugin-flowtype": "3.10.3", "eslint-plugin-flowtype": "3.11.1",
"eslint-plugin-import": "2.17.3", "eslint-plugin-import": "2.18.0",
"eslint-plugin-jest": "22.6.4", "eslint-plugin-jest": "22.7.1",
"eslint-plugin-jsx-a11y": "^6.0.3", "eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "7.13.0", "eslint-plugin-react": "7.14.2",
"ethereumjs-abi": "^0.6.7", "ethereumjs-abi": "^0.6.7",
"extract-text-webpack-plugin": "^4.0.0-beta.0", "extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "4.0.0", "file-loader": "4.0.0",
"flow-bin": "0.101.0", "flow-bin": "0.102.0",
"fs-extra": "8.0.1", "fs-extra": "8.0.1",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"html-webpack-plugin": "^3.0.4", "html-webpack-plugin": "^3.0.4",
@ -128,13 +129,13 @@
"storybook-host": "^5.0.3", "storybook-host": "^5.0.3",
"storybook-router": "^0.3.3", "storybook-router": "^0.3.3",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"truffle": "5.0.22", "truffle": "5.0.24",
"truffle-contract": "4.0.20", "truffle-contract": "4.0.21",
"truffle-solidity-loader": "0.1.21", "truffle-solidity-loader": "0.1.23",
"uglifyjs-webpack-plugin": "2.1.3", "uglifyjs-webpack-plugin": "2.1.3",
"webpack": "4.34.0", "webpack": "4.35.0",
"webpack-bundle-analyzer": "3.3.2", "webpack-bundle-analyzer": "3.3.2",
"webpack-cli": "3.3.4", "webpack-cli": "3.3.5",
"webpack-dev-server": "3.7.2", "webpack-dev-server": "3.7.2",
"webpack-manifest-plugin": "^2.0.0-rc.2" "webpack-manifest-plugin": "^2.0.0-rc.2"
} }

View File

@ -43,13 +43,12 @@ git clone https://github.com/gnosis/safe-contracts.git
cd safe-contracts cd safe-contracts
yarn yarn
ganache-cli -l 7000000 ganache-cli -l 7000000
npx truffle compile
npx truffle migrate npx truffle migrate
``` ```
2. Compiling Token Contracts for the tests: 2. Migrate Token Contracts for the tests:
Inside `safe-react` directory Inside `safe-react` directory
``` ```
npx truffle compile npx truffle migrate
``` ```
3. Run the tests: 3. Run the tests:
``` ```

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -6,7 +6,7 @@ import styles from './index.scss'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4'; type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4'
type Props = { type Props = {
align?: 'left' | 'center' | 'right', align?: 'left' | 'center' | 'right',
@ -15,24 +15,18 @@ type Props = {
tag: HeadingTag, tag: HeadingTag,
truncate?: boolean, truncate?: boolean,
children: React.Node, children: React.Node,
testId?: string,
} }
class Heading extends React.PureComponent<Props> { class Heading extends React.PureComponent<Props> {
render() { render() {
const { const {
align, tag, truncate, margin, color, children, ...props align, tag, truncate, margin, color, children, testId, ...props
} = this.props } = this.props
const className = cx( const className = cx('heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, { truncate })
'heading',
align,
tag,
margin ? capitalize(margin, 'margin') : undefined,
color,
{ truncate },
)
return React.createElement(tag, { ...props, className }, children) return React.createElement(tag, { ...props, className, 'data-testid': testId || '' }, children)
} }
} }

View File

@ -1,11 +1,11 @@
// @flow // @flow
import * as React from 'react' import React, { useState, useEffect } from 'react'
import Block from '~/components/layout/Block'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Block from '~/components/layout/Block'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import { required } from '~/components/forms/validator' import { required } from '~/components/forms/validator'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import OpenPaper from '~/components/Stepper/OpenPaper' import OpenPaper from '~/components/Stepper/OpenPaper'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
@ -78,10 +78,6 @@ type Props = LayoutProps & {
updateInitialProps: (initialValues: Object) => void, updateInitialProps: (initialValues: Object) => void,
} }
type State = {
owners: Array<string>,
}
const calculateSafeValues = (owners: Array<string>, threshold: Number, values: Object) => { const calculateSafeValues = (owners: Array<string>, threshold: Number, values: Object) => {
const initialValues = { ...values } const initialValues = { ...values }
for (let i = 0; i < owners.length; i += 1) { for (let i = 0; i < owners.length; i += 1) {
@ -91,41 +87,35 @@ const calculateSafeValues = (owners: Array<string>, threshold: Number, values: O
return initialValues return initialValues
} }
class OwnerListComponent extends React.PureComponent<Props, State> { const OwnerListComponent = (props: Props) => {
state = { const [owners, setOwners] = useState<Array<string>>([])
owners: [], const {
} values, updateInitialProps, network, classes,
} = props
mounted = false useEffect(() => {
let isCurrent = true
componentDidMount = async () => { const fetchSafe = async () => {
this.mounted = true
const { values, updateInitialProps } = this.props
const safeAddress = values[FIELD_LOAD_ADDRESS] const safeAddress = values[FIELD_LOAD_ADDRESS]
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const owners = await gnosisSafe.getOwners() const safeOwners = await gnosisSafe.getOwners()
const threshold = await gnosisSafe.getThreshold() const threshold = await gnosisSafe.getThreshold()
const initialValues = calculateSafeValues(owners.sort(), threshold, values) if (isCurrent && owners) {
const sortedOwners = safeOwners.sort()
const initialValues = calculateSafeValues(sortedOwners, threshold, values)
updateInitialProps(initialValues) updateInitialProps(initialValues)
setOwners(sortedOwners)
if (!owners) {
return
}
if (this.mounted) {
this.setState(() => ({ owners: owners.sort() }))
} }
} }
componentWillUnmount() { fetchSafe()
this.mounted = false
}
render() { return () => {
const { network, classes } = this.props isCurrent = false
const { owners } = this.state }
}, [])
return ( return (
<React.Fragment> <React.Fragment>
@ -141,8 +131,8 @@ class OwnerListComponent extends React.PureComponent<Props, State> {
</Row> </Row>
<Hairline /> <Hairline />
<Block margin="md" padding="md"> <Block margin="md" padding="md">
{owners.map((x, index) => ( {owners.map((address, index) => (
<Row key={owners[index].address} className={classes.owner}> <Row key={address} className={classes.owner}>
<Col xs={4}> <Col xs={4}>
<Field <Field
className={classes.name} className={classes.name}
@ -150,18 +140,18 @@ class OwnerListComponent extends React.PureComponent<Props, State> {
component={TextField} component={TextField}
type="text" type="text"
validate={required} validate={required}
defaultValue={`Owner #${index + 1}`} initialValue={`Owner #${index + 1}`}
placeholder="Owner Name*" placeholder="Owner Name*"
text="Owner Name" text="Owner Name"
/> />
</Col> </Col>
<Col xs={7}> <Col xs={7}>
<Row className={classes.ownerAddresses}> <Row className={classes.ownerAddresses}>
<Identicon address={owners[index]} diameter={32} /> <Identicon address={address} diameter={32} />
<Paragraph size="md" color="disabled" noMargin className={classes.address}> <Paragraph size="md" color="disabled" noMargin className={classes.address}>
{owners[index]} {address}
</Paragraph> </Paragraph>
<Link className={classes.open} to={getEtherScanLink(owners[index], network)} target="_blank"> <Link className={classes.open} to={getEtherScanLink(address, network)} target="_blank">
<OpenInNew style={openIconStyle} /> <OpenInNew style={openIconStyle} />
</Link> </Link>
</Row> </Row>
@ -172,18 +162,13 @@ class OwnerListComponent extends React.PureComponent<Props, State> {
</React.Fragment> </React.Fragment>
) )
} }
}
const OwnerListPage = withStyles(styles)(OwnerListComponent) const OwnerListPage = withStyles(styles)(OwnerListComponent)
const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => ( const OwnerList = ({ updateInitialProps }: Object, network: string) => (controls: React$Node, { values }: Object) => (
<React.Fragment> <React.Fragment>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>
<OwnerListPage <OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
network={network}
updateInitialProps={updateInitialProps}
values={values}
/>
</OpenPaper> </OpenPaper>
</React.Fragment> </React.Fragment>
) )

View File

@ -1,4 +1,4 @@
// @flow // @flow
export const FIELD_LOAD_NAME: string = 'name' export const FIELD_LOAD_NAME: string = 'name'
export const FIELD_LOAD_ADDRESS: string = 'address' export const FIELD_LOAD_ADDRESS: string = 'address'
export const THRESHOLD: Number = 'threshold' export const THRESHOLD: string = 'threshold'

View File

@ -23,8 +23,7 @@ import OpenPaper from '~/components/Stepper/OpenPaper'
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor' import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import { md, lg, sm } from '~/theme/variables' import { md, lg, sm } from '~/theme/variables'
import trash from '~/assets/icons/trash.svg'
const trash = require('../../assets/trash.svg')
type Props = { type Props = {
classes: Object, classes: Object,

View File

@ -42,7 +42,7 @@ const Tokens = (props: Props) => {
return ( return (
<React.Fragment> <React.Fragment>
<Row align="center" grow className={classes.heading}> <Row align="center" grow className={classes.heading}>
<Paragraph className={classes.manage} noMargin> <Paragraph className={classes.manage} noMargin weight="bolder">
Manage Tokens Manage Tokens
</Paragraph> </Paragraph>
<IconButton onClick={onClose} disableRipple data-testid={MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID}> <IconButton onClick={onClose} disableRipple data-testid={MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID}>

View File

@ -3,10 +3,10 @@ import * as React from 'react'
import OpenInNew from '@material-ui/icons/OpenInNew' import OpenInNew from '@material-ui/icons/OpenInNew'
import Tabs from '@material-ui/core/Tabs' import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab' import Tab from '@material-ui/core/Tab'
import { withStyles } from '@material-ui/core/styles'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import { withStyles } from '@material-ui/core/styles'
import Heading from '~/components/layout/Heading' import Heading from '~/components/layout/Heading'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link' import Link from '~/components/layout/Link'
@ -21,10 +21,14 @@ import { copyToClipboard } from '~/utils/clipboard'
import Balances from './Balances' import Balances from './Balances'
import Settings from './Settings' import Settings from './Settings'
export const SETTINGS_TAB_BTN_TESTID = 'settings-tab-btn'
export const SAFE_VIEW_NAME_HEADING_TESTID = 'safe-name-heading'
type Props = SelectorProps & { type Props = SelectorProps & {
classes: Object, classes: Object,
granted: boolean, granted: boolean,
createTransaction: Function, createTransaction: Function,
updateSafe: Function,
} }
type State = { type State = {
@ -90,7 +94,15 @@ class Layout extends React.Component<Props, State> {
render() { render() {
const { const {
safe, provider, network, classes, granted, tokens, activeTokens, createTransaction, safe,
provider,
network,
classes,
granted,
tokens,
activeTokens,
createTransaction,
updateSafe,
} = this.props } = this.props
const { tabIndex } = this.state const { tabIndex } = this.state
@ -107,7 +119,7 @@ class Layout extends React.Component<Props, State> {
<Identicon address={address} diameter={50} /> <Identicon address={address} diameter={50} />
<Block className={classes.name}> <Block className={classes.name}>
<Row> <Row>
<Heading tag="h2" color="secondary"> <Heading tag="h2" color="secondary" testId={SAFE_VIEW_NAME_HEADING_TESTID}>
{name} {name}
</Heading> </Heading>
{!granted && <Block className={classes.readonly}>Read Only</Block>} {!granted && <Block className={classes.readonly}>Read Only</Block>}
@ -126,7 +138,7 @@ class Layout extends React.Component<Props, State> {
<Tabs value={tabIndex} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary"> <Tabs value={tabIndex} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary">
<Tab label="Balances" /> <Tab label="Balances" />
<Tab label="Transactions" /> <Tab label="Transactions" />
<Tab label="Settings" /> <Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TESTID} />
</Tabs> </Tabs>
</Row> </Row>
<Hairline color="#c8ced4" /> <Hairline color="#c8ced4" />
@ -148,6 +160,7 @@ class Layout extends React.Component<Props, State> {
safeAddress={address} safeAddress={address}
safeName={name} safeName={name}
etherScanLink={etherScanLink} etherScanLink={etherScanLink}
updateSafe={updateSafe}
threshold={safe.threshold} threshold={safe.threshold}
owners={safe.owners} owners={safe.owners}
createTransaction={createTransaction} createTransaction={createTransaction}

View File

@ -0,0 +1,94 @@
// @flow
import React from 'react'
import { withStyles } from '@material-ui/core/styles'
import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import Field from '~/components/forms/Field'
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
import TextField from '~/components/forms/TextField'
import GnoForm from '~/components/forms/GnoForm'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph'
import Hairline from '~/components/layout/Hairline'
import Button from '~/components/layout/Button'
import { sm, boldFont } from '~/theme/variables'
import { styles } from './style'
const controlsStyle = {
backgroundColor: 'white',
padding: sm,
}
const saveButtonStyle = {
marginRight: sm,
fontWeight: boldFont,
}
export const SAFE_NAME_INPUT_TESTID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TESTID = 'change-safe-name-btn'
type Props = {
classes: Object,
safeAddress: string,
safeName: string,
updateSafe: Function,
}
const ChangeSafeName = (props: Props) => {
const {
classes, safeAddress, safeName, updateSafe,
} = props
const handleSubmit = (values) => {
updateSafe({ address: safeAddress, name: values.safeName })
}
return (
<React.Fragment>
<GnoForm onSubmit={handleSubmit}>
{() => (
<React.Fragment>
<Block className={classes.formContainer}>
<Paragraph noMargin className={classes.title} size="lg" weight="bolder">
Modify Safe name
</Paragraph>
<Paragraph size="sm">
You can change the name of this Safe. This name is only stored locally and never shared with Gnosis or
any third parties.
</Paragraph>
<Block className={classes.root}>
<Field
name="safeName"
component={TextField}
type="text"
validate={composeValidators(required, minMaxLength(1, 50))}
placeholder="Safe name*"
text="Safe name*"
defaultValue={safeName}
testId={SAFE_NAME_INPUT_TESTID}
/>
</Block>
</Block>
<Hairline />
<Row style={controlsStyle} align="end" grow>
<Col end="xs">
<Button
type="submit"
style={saveButtonStyle}
size="small"
variant="contained"
color="primary"
testId={SAFE_NAME_SUBMIT_BTN_TESTID}
>
SAVE
</Button>
</Col>
</Row>
</React.Fragment>
)}
</GnoForm>
</React.Fragment>
)
}
export default withStyles(styles)(ChangeSafeName)

View File

@ -0,0 +1,17 @@
// @flow
import { lg } from '~/theme/variables'
export const styles = () => ({
title: {
padding: `${lg} 0 20px`,
fontSize: '16px',
},
formContainer: {
padding: '0 20px',
minHeight: '369px',
},
root: {
display: 'flex',
maxWidth: '460px',
},
})

View File

@ -18,7 +18,7 @@ import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import actions, { type Actions } from './actions' import actions, { type Actions } from './actions'
import { lg, md, secondary } from '~/theme/variables' import { secondary } from '~/theme/variables'
import { styles } from './style' import { styles } from './style'
const openIconStyle = { const openIconStyle = {
@ -36,22 +36,11 @@ type Props = Actions & {
} }
const RemoveSafeComponent = ({ const RemoveSafeComponent = ({
onClose, onClose, isOpen, classes, safeAddress, etherScanLink, safeName, removeSafe,
isOpen,
classes,
safeAddress,
etherScanLink,
safeName,
removeSafe,
}: Props) => ( }: Props) => (
<Modal <Modal title="Remove Safe" description="Remove the selected Safe" handleClose={onClose} open={isOpen}>
title="Remove Safe"
description="Remove the selected Safe"
handleClose={onClose}
open={isOpen}
>
<Row align="center" grow className={classes.heading}> <Row align="center" grow className={classes.heading}>
<Paragraph className={classes.manage} noMargin> <Paragraph className={classes.manage} noMargin weight="bolder">
Remove Safe Remove Safe
</Paragraph> </Paragraph>
<IconButton onClick={onClose} disableRipple> <IconButton onClick={onClose} disableRipple>
@ -66,7 +55,7 @@ const RemoveSafeComponent = ({
</Col> </Col>
<Col xs={11}> <Col xs={11}>
<Block className={classNames(classes.name, classes.userName)}> <Block className={classNames(classes.name, classes.userName)}>
<Paragraph size="lg" noMargin> <Paragraph size="lg" noMargin weight="bolder">
{safeName} {safeName}
</Paragraph> </Paragraph>
<Block align="center" className={classes.user}> <Block align="center" className={classes.user}>
@ -84,8 +73,8 @@ const RemoveSafeComponent = ({
<Row className={classes.description}> <Row className={classes.description}>
<Paragraph noMargin> <Paragraph noMargin>
Removing a Safe only removes it from your interface. Removing a Safe only removes it from your interface.
<b>It does not delete the Safe</b>. <b>It does not delete the Safe</b>
You can always add it back using the Safe's address. . You can always add it back using the Safe&apos;s address.
</Paragraph> </Paragraph>
</Row> </Row>
</Block> </Block>

View File

@ -14,7 +14,6 @@ import ChangeThreshold from './ChangeThreshold'
import type { Owner } from '~/routes/safe/store/models/owner' import type { Owner } from '~/routes/safe/store/models/owner'
import { styles } from './style' import { styles } from './style'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
type Props = { type Props = {
owners: List<Owner>, owners: List<Owner>,

View File

@ -1,56 +0,0 @@
// @flow
import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph'
import Button from '~/components/layout/Button'
import { sm, boldFont } from '~/theme/variables'
import { styles } from './style'
const controlsStyle = {
backgroundColor: 'white',
padding: sm,
}
const saveButtonStyle = {
marginRight: sm,
fontWeight: boldFont,
}
type Props = {
classes: Object,
}
class UpdateSafeName extends React.Component<Props, State> {
render() {
const { classes } = this.props
return (
<React.Fragment>
<Block margin="lg">
<Paragraph size="lg" color="primary" noMargin>
Details
</Paragraph>
</Block>
<Row style={controlsStyle} align="end" grow>
<Col end="xs">
<Button
type="submit"
style={saveButtonStyle}
size="small"
variant="contained"
color="primary"
onClick={() => {}}
>
SAVE
</Button>
</Col>
</Row>
</React.Fragment>
)
}
}
export default withStyles(styles)(UpdateSafeName)

View File

@ -1,4 +0,0 @@
// @flow
import { lg, border } from '~/theme/variables'
export const styles = () => ({})

View File

@ -9,7 +9,8 @@ import Row from '~/components/layout/Row'
import RemoveSafeModal from './RemoveSafeModal' import RemoveSafeModal from './RemoveSafeModal'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner' import { type Owner } from '~/routes/safe/store/models/owner'
import ChangeSafeName from './ChangeSafeName'
import ThresholdSettings from './ThresholdSettings' import ThresholdSettings from './ThresholdSettings'
import { styles } from './style' import { styles } from './style'
@ -27,6 +28,7 @@ type Props = {
owners: List<Owner>, owners: List<Owner>,
threshold: number, threshold: number,
createTransaction: Function, createTransaction: Function,
updateSafe: Function,
} }
type Action = 'RemoveSafe' type Action = 'RemoveSafe'
@ -52,14 +54,24 @@ class Settings extends React.Component<Props, State> {
render() { render() {
const { showRemoveSafe, menuOptionIndex } = this.state const { showRemoveSafe, menuOptionIndex } = this.state
const { const {
classes, granted, etherScanLink, safeAddress, safeName, owners, threshold, createTransaction, classes,
granted,
etherScanLink,
safeAddress,
safeName,
updateSafe,
owners,
threshold,
createTransaction,
} = this.props } = this.props
return ( return (
<React.Fragment> <React.Fragment>
<Row align="center" className={classes.message}> <Row align="center" className={classes.message}>
<Col xs={6}> <Col xs={6}>
<Paragraph className={classes.settings}>Settings</Paragraph> <Paragraph className={classes.settings} size="lg" weight="bolder">
Settings
</Paragraph>
</Col> </Col>
<Col xs={6} end="sm"> <Col xs={6} end="sm">
<Paragraph noMargin size="md" color="error" className={classes.links} onClick={this.onShow('RemoveSafe')}> <Paragraph noMargin size="md" color="error" className={classes.links} onClick={this.onShow('RemoveSafe')}>
@ -113,7 +125,9 @@ class Settings extends React.Component<Props, State> {
</Col> </Col>
<Col xs={9} layout="column"> <Col xs={9} layout="column">
<Block className={classes.container}> <Block className={classes.container}>
{menuOptionIndex === 1 && <p>To be done</p>} {menuOptionIndex === 1 && (
<ChangeSafeName safeAddress={safeAddress} safeName={safeName} updateSafe={updateSafe} />
)}
{granted && menuOptionIndex === 2 && <p>To be done</p>} {granted && menuOptionIndex === 2 && <p>To be done</p>}
{granted && menuOptionIndex === 3 && ( {granted && menuOptionIndex === 3 && (
<ThresholdSettings <ThresholdSettings

View File

@ -1,9 +1,9 @@
// @flow // @flow
import { import {
sm, md, lg, border, secondary, bolderFont, sm, lg, border, secondary, bolderFont,
} from '~/theme/variables' } from '~/theme/variables'
export const styles = (theme: Object) => ({ export const styles = () => ({
root: { root: {
backgroundColor: 'white', backgroundColor: 'white',
boxShadow: '0 -1px 4px 0 rgba(74, 85, 121, 0.5)', boxShadow: '0 -1px 4px 0 rgba(74, 85, 121, 0.5)',

View File

@ -2,15 +2,18 @@
import fetchSafe from '~/routes/safe/store/actions/fetchSafe' import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
import fetchTokenBalances from '~/routes/safe/store/actions/fetchTokenBalances' import fetchTokenBalances from '~/routes/safe/store/actions/fetchTokenBalances'
import createTransaction from '~/routes/safe/store/actions/createTransaction' import createTransaction from '~/routes/safe/store/actions/createTransaction'
import updateSafe from '~/routes/safe/store/actions/updateSafe'
export type Actions = { export type Actions = {
fetchSafe: typeof fetchSafe, fetchSafe: typeof fetchSafe,
fetchTokenBalances: typeof fetchTokenBalances, fetchTokenBalances: typeof fetchTokenBalances,
createTransaction: typeof createTransaction, createTransaction: typeof createTransaction,
updateSafe: typeof updateSafe,
} }
export default { export default {
fetchSafe, fetchSafe,
fetchTokenBalances, fetchTokenBalances,
createTransaction, createTransaction,
updateSafe,
} }

View File

@ -29,8 +29,9 @@ class SafeView extends React.Component<Props> {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { activeTokens } = this.props const { activeTokens } = this.props
const oldActiveTokensSize = prevProps.activeTokens.size
if (activeTokens.size > prevProps.activeTokens.size) { if (oldActiveTokensSize > 0 && activeTokens.size > oldActiveTokensSize) {
this.checkForUpdates() this.checkForUpdates()
} }
} }
@ -52,7 +53,15 @@ class SafeView extends React.Component<Props> {
render() { render() {
const { const {
safe, provider, activeTokens, granted, userAddress, network, tokens, createTransaction, safe,
provider,
activeTokens,
granted,
userAddress,
network,
tokens,
createTransaction,
updateSafe,
} = this.props } = this.props
return ( return (
@ -66,6 +75,7 @@ class SafeView extends React.Component<Props> {
network={network} network={network}
granted={granted} granted={granted}
createTransaction={createTransaction} createTransaction={createTransaction}
updateSafe={updateSafe}
/> />
</Page> </Page>
) )

View File

@ -61,6 +61,10 @@ describe('DOM > Feature > LOAD a safe', () => {
fireEvent.submit(form) fireEvent.submit(form)
await sleep(400) await sleep(400)
// submit form with owners names
fireEvent.submit(form)
await sleep(400)
// Submit // Submit
fireEvent.submit(form) fireEvent.submit(form)
const deployedAddress = await whenSafeDeployed() const deployedAddress = await whenSafeDeployed()

View File

@ -0,0 +1,46 @@
// @flow
import { fireEvent, cleanup } from '@testing-library/react'
import { aNewStore } from '~/store'
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
import { renderSafeView } from '~/test/builder/safe.dom.utils'
import { sleep } from '~/utils/timer'
import 'jest-dom/extend-expect'
import { SETTINGS_TAB_BTN_TESTID, SAFE_VIEW_NAME_HEADING_TESTID } from '~/routes/safe/components/Layout'
import { SAFE_NAME_INPUT_TESTID, SAFE_NAME_SUBMIT_BTN_TESTID } from '~/routes/safe/components/Settings/ChangeSafeName'
afterEach(cleanup)
describe('DOM > Feature > Settings', () => {
let store
let safeAddress
beforeEach(async () => {
store = aNewStore()
// using 4th account because other accounts were used in other tests and paid gas
safeAddress = await aMinedSafe(store)
})
it('Changes safe name', async () => {
const INITIAL_NAME = 'Safe Name'
const NEW_NAME = 'NEW SAFE NAME'
const SafeDom = renderSafeView(store, safeAddress)
await sleep(1300)
const safeNameHeading = SafeDom.getByTestId(SAFE_VIEW_NAME_HEADING_TESTID)
expect(safeNameHeading).toHaveTextContent(INITIAL_NAME)
// Open settings tab
// Safe name setting screen should be pre-selected
const settingsBtn = SafeDom.getByTestId(SETTINGS_TAB_BTN_TESTID)
fireEvent.click(settingsBtn)
// Change the name
const safeNameInput = SafeDom.getByTestId(SAFE_NAME_INPUT_TESTID)
const submitBtn = SafeDom.getByTestId(SAFE_NAME_SUBMIT_BTN_TESTID)
fireEvent.change(safeNameInput, { target: { value: NEW_NAME } })
fireEvent.click(submitBtn)
// Check if the name changed
expect(safeNameHeading).toHaveTextContent(NEW_NAME)
})
})

File diff suppressed because it is too large Load Diff

1215
yarn.lock

File diff suppressed because it is too large Load Diff