Add etherscan/copy buttons in load/create safe flows

This commit is contained in:
Mikhail Mikheev 2019-09-11 19:35:36 +04:00
parent b44c30923d
commit 3c05220008
12 changed files with 138 additions and 74 deletions

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<path fill="#B2B5B2" fill-rule="nonzero" d="M14 13h2V6h-5v2h2a1 1 0 0 1 1 1v4zM9 8V5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-3v3a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h3zm-2 2v7h5v-7H7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 351 B

View File

@ -0,0 +1,35 @@
// @flow
import React from 'react'
import { withStyles } from '@material-ui/core/styles'
import Img from '~/components/layout/Img'
import { copyToClipboard } from '~/utils/clipboard'
import { xs } from '~/theme/variables'
import CopyIcon from './copy.svg'
const styles = () => ({
container: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
padding: xs,
borderRadius: '50%',
transition: 'background-color .2s ease-in-out',
'&:hover': {
backgroundColor: '#F0EFEE',
},
},
})
type CopyBtnProps = {
content: string,
classes: Object,
}
const CopyBtn = ({ content, classes }: CopyBtnProps) => (navigator.clipboard ? (
<div className={classes.container}>
<Img src={CopyIcon} height={20} alt="Click to copy" onClick={() => copyToClipboard(content)} />
</div>
) : null)
export default withStyles(styles)(CopyBtn)

View File

@ -0,0 +1,53 @@
// @flow
import React from 'react'
import { withStyles } from '@material-ui/core/styles'
import { connect } from 'react-redux'
import Img from '~/components/layout/Img'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { xs } from '~/theme/variables'
import { networkSelector } from '~/logic/wallets/store/selectors'
import SearchIcon from './search.svg'
const styles = () => ({
container: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: xs,
borderRadius: '50%',
transition: 'background-color .2s ease-in-out',
'&:hover': {
backgroundColor: '#F0EFEE',
},
},
})
type EtherscanBtnProps = {
type: 'tx' | 'address',
value: string,
currentNetwork: string,
classes: Object,
}
const EtherscanBtn = ({
type, value, currentNetwork, classes,
}: EtherscanBtnProps) => (
<a
className={classes.container}
href={getEtherScanLink(type, value, currentNetwork)}
target="_blank"
rel="noopener noreferrer"
title="Show Details on etherscan"
aria-label="Show Details on etherscan"
>
<Img src={SearchIcon} height={20} alt="Etherscan" />
</a>
)
const EtherscanBtnWithStyles = withStyles(styles)(EtherscanBtn)
export default connect<Object, Object, ?Function, ?Object>(
(state) => ({ currentNetwork: networkSelector(state) }),
null,
)(EtherscanBtnWithStyles)

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<path fill="#B2B5B2" fill-rule="nonzero" d="M18.671 17.085a1.119 1.119 0 0 1-.002 1.587 1.126 1.126 0 0 1-1.586.003l-2.68-2.68a6.6 6.6 0 1 1 .862-10.061 6.603 6.603 0 0 1 .727 8.471l2.68 2.68zm-4.923-3.335a4.455 4.455 0 0 0-6.298-6.3 4.456 4.456 0 0 0 0 6.3 4.452 4.452 0 0 0 6.298 0z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 436 B

View File

@ -26,6 +26,6 @@ const EtherscanLink = ({ type, value, currentNetwork }: EtherscanLinkProps) => (
)
export default connect<Object, Object, ?Function, ?Object>(
state => ({ currentNetwork: networkSelector(state) }),
(state) => ({ currentNetwork: networkSelector(state) }),
null,
)(EtherscanLink)

View File

@ -1,7 +1,6 @@
// @flow
import React, { useState, useEffect } from 'react'
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 { required } from '~/components/forms/validator'
@ -10,22 +9,17 @@ import Identicon from '~/components/Identicon'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph'
import Hairline from '~/components/layout/Hairline'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import {
sm, md, lg, border, secondary, disabled, extraSmallFontSize,
sm, md, lg, border, disabled, extraSmallFontSize,
} from '~/theme/variables'
import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { FIELD_LOAD_ADDRESS, THRESHOLD } from '~/routes/load/components/fields'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
const openIconStyle = {
height: '16px',
color: secondary,
}
const styles = () => ({
details: {
padding: lg,
@ -45,6 +39,7 @@ const styles = () => ({
},
address: {
paddingLeft: '6px',
marginRight: sm,
},
open: {
paddingLeft: sm,
@ -70,11 +65,7 @@ const styles = () => ({
},
})
type LayoutProps = {
network: string,
}
type Props = LayoutProps & {
type Props = {
values: Object,
classes: Object,
updateInitialProps: (initialValues: Object) => void,
@ -92,7 +83,7 @@ const calculateSafeValues = (owners: Array<string>, threshold: Number, values: O
const OwnerListComponent = (props: Props) => {
const [owners, setOwners] = useState<Array<string>>([])
const {
values, updateInitialProps, network, classes,
values, updateInitialProps, classes,
} = props
useEffect(() => {
@ -153,9 +144,8 @@ const OwnerListComponent = (props: Props) => {
<Paragraph size="md" color="disabled" noMargin className={classes.address}>
{address}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', address, network)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={address} />
<EtherscanBtn type="address" value={address} />
</Row>
</Col>
</Row>

View File

@ -2,29 +2,23 @@
import * as React from 'react'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Block from '~/components/layout/Block'
import Identicon from '~/components/Identicon'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import EtherscanBtn from '~/components/EtherscanBtn'
import Paragraph from '~/components/layout/Paragraph'
import CopyBtn from '~/components/CopyBtn'
import Hairline from '~/components/layout/Hairline'
import {
xs, sm, lg, border, secondary,
xs, sm, lg, border,
} from '~/theme/variables'
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import { getOwnerNameBy, getOwnerAddressBy, getNumOwnersFrom } from '~/routes/open/components/fields'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS, THRESHOLD } from '~/routes/load/components/fields'
const openIconStyle = {
height: '16px',
color: secondary,
}
const styles = () => ({
root: {
minHeight: '300px',
@ -51,6 +45,9 @@ const styles = () => ({
},
user: {
justifyContent: 'left',
'& > p': {
marginRight: sm,
},
},
open: {
paddingLeft: sm,
@ -65,15 +62,12 @@ const styles = () => ({
},
address: {
paddingLeft: '6px',
marginRight: sm,
},
})
type LayoutProps = {
network: string,
type Props = {
userAddress: string,
}
type Props = LayoutProps & {
values: Object,
classes: Object,
}
@ -97,9 +91,7 @@ const checkUserAddressOwner = (values: Object, userAddress: string): boolean =>
class ReviewComponent extends React.PureComponent<Props, State> {
render() {
const {
values, classes, network, userAddress,
} = this.props
const { values, classes, userAddress } = this.props
const isOwner = checkUserAddressOwner(values, userAddress)
const owners = getAccountsFrom(values)
@ -132,9 +124,8 @@ class ReviewComponent extends React.PureComponent<Props, State> {
<Paragraph size="md" color="disabled" noMargin className={classes.address}>
{shortVersionOf(safeAddress, 4)}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', safeAddress, network)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={safeAddress} />
<EtherscanBtn type="address" value={safeAddress} />
</Row>
</Block>
<Block margin="lg">
@ -177,13 +168,8 @@ class ReviewComponent extends React.PureComponent<Props, State> {
<Paragraph size="md" color="disabled" noMargin>
{address}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', address, network)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={address} />
<EtherscanBtn type="address" value={address} />
</Block>
</Block>
</Col>

View File

@ -2,30 +2,25 @@
import * as React from 'react'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import { estimateGasForDeployingSafe } from '~/logic/contracts/safeContracts'
import { getNamesFrom, getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Block from '~/components/layout/Block'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph'
import {
sm, md, lg, border, secondary, background,
sm, md, lg, border, background,
} from '~/theme/variables'
import Hairline from '~/components/layout/Hairline'
import { getEtherScanLink, getWeb3 } from '~/logic/wallets/getWeb3'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { FIELD_NAME, FIELD_CONFIRMATIONS, getNumOwnersFrom } from '../fields'
const { useEffect, useState } = React
const openIconStyle = {
height: '16px',
color: secondary,
}
const styles = () => ({
root: {
minHeight: '300px',
@ -58,6 +53,9 @@ const styles = () => ({
},
user: {
justifyContent: 'left',
'& > p': {
marginRight: sm,
},
},
open: {
paddingLeft: sm,
@ -69,15 +67,12 @@ const styles = () => ({
})
type Props = {
network: string,
values: Object,
classes: Object,
userAccount: string,
}
const ReviewComponent = ({
values, classes, network, userAccount,
}: Props) => {
const ReviewComponent = ({ values, classes, userAccount }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('0.00')
const names = getNamesFrom(values)
const addresses = getAccountsFrom(values)
@ -152,13 +147,8 @@ const ReviewComponent = ({
<Paragraph size="md" color="disabled" noMargin>
{addresses[index]}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', addresses[index], network)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={addresses[index]} />
<EtherscanBtn type="address" value={addresses[index]} />
</Block>
</Block>
</Col>
@ -184,10 +174,10 @@ ETH in this wallet to fund this transaction.
const ReviewPage = withStyles(styles)(ReviewComponent)
const Review = ({ network }: LayoutProps) => (controls: React.Node, { values }: Object) => (
const Review = () => (controls: React.Node, { values }: Object) => (
<>
<OpenPaper controls={controls} padding={false}>
<ReviewPage network={network} values={values} />
<ReviewPage values={values} />
</OpenPaper>
</>
)

View File

@ -2,7 +2,7 @@
import { createStructuredSelector } from 'reselect'
import { providerNameSelector, userAccountSelector, networkSelector } from '~/logic/wallets/store/selectors'
export default createStructuredSelector({
export default createStructuredSelector<Object, *>({
provider: providerNameSelector,
network: networkSelector,
userAccount: userAccountSelector,

View File

@ -75,7 +75,7 @@ type Props = {
const Receive = ({
classes, onClose, safeAddress, safeName, etherScanLink,
}: Props) => (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph className={classes.manage} weight="bolder" noMargin>
Receive funds
@ -113,7 +113,7 @@ const Receive = ({
Done
</Button>
</Row>
</React.Fragment>
</>
)
export default withStyles(styles)(Receive)

View File

@ -40,7 +40,7 @@ const Tokens = (props: Props) => {
} = props
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph className={classes.manage} noMargin weight="bolder">
Manage Tokens
@ -72,7 +72,7 @@ const Tokens = (props: Props) => {
tokens={tokens}
/>
)}
</React.Fragment>
</>
)
}

View File

@ -24,7 +24,7 @@ export const styles = () => ({
alignItems: 'center',
cursor: 'pointer',
'&:first-child': {
borderRadius: '8px',
borderTopLeftRadius: '8px',
},
},
active: {