Pull from dev, conflict fixes
This commit is contained in:
commit
5451f32ff4
|
@ -58,7 +58,8 @@
|
|||
"react-infinite-scroll-component": "4.5.3",
|
||||
"react-qr-reader": "^2.2.1",
|
||||
"react-redux": "7.1.1",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-dom": "5.1.2",
|
||||
"react-window": "^1.8.5",
|
||||
"recompose": "^0.30.0",
|
||||
"redux": "4.0.4",
|
||||
"redux-actions": "^2.6.5",
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// @flow
|
||||
import React, { useState } from 'react'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } 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 = () => ({
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
|
@ -20,19 +20,24 @@ const styles = () => ({
|
|||
backgroundColor: '#F0EFEE',
|
||||
},
|
||||
},
|
||||
inreasedPopperZindex: {
|
||||
zIndex: 2001,
|
||||
},
|
||||
})
|
||||
|
||||
type CopyBtnProps = {
|
||||
content: string,
|
||||
classes: Object,
|
||||
increaseZindex?: boolean,
|
||||
}
|
||||
|
||||
const CopyBtn = ({ content, classes }: CopyBtnProps) => {
|
||||
const CopyBtn = ({ content, increaseZindex = false }: CopyBtnProps) => {
|
||||
if (!navigator.clipboard) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [clicked, setClicked] = useState<boolean>(false)
|
||||
const classes = useStyles()
|
||||
const customClasses = increaseZindex ? { popper: classes.inreasedPopperZindex } : {}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
@ -47,6 +52,7 @@ const CopyBtn = ({ content, classes }: CopyBtnProps) => {
|
|||
}
|
||||
}, 300)
|
||||
}}
|
||||
classes={customClasses}
|
||||
>
|
||||
<div className={classes.container}>
|
||||
<Img
|
||||
|
@ -63,4 +69,4 @@ const CopyBtn = ({ content, classes }: CopyBtnProps) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CopyBtn)
|
||||
export default CopyBtn
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Img from '~/components/layout/Img'
|
||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
||||
import { xs } from '~/theme/variables'
|
||||
import SearchIcon from './search.svg'
|
||||
|
||||
const styles = () => ({
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
|
@ -19,30 +19,34 @@ const styles = () => ({
|
|||
backgroundColor: '#F0EFEE',
|
||||
},
|
||||
},
|
||||
inreasedPopperZindex: {
|
||||
zIndex: 2001,
|
||||
},
|
||||
})
|
||||
|
||||
type EtherscanBtnProps = {
|
||||
type: 'tx' | 'address',
|
||||
value: string,
|
||||
classes: Object,
|
||||
increaseZindex?: boolean,
|
||||
}
|
||||
|
||||
const EtherscanBtn = ({
|
||||
type, value, classes,
|
||||
}: EtherscanBtnProps) => (
|
||||
<Tooltip title="Show details on Etherscan" placement="top">
|
||||
<a
|
||||
className={classes.container}
|
||||
href={getEtherScanLink(type, value)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Show details on Etherscan"
|
||||
>
|
||||
<Img src={SearchIcon} height={20} alt="Etherscan" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)
|
||||
const EtherscanBtn = ({ type, value, increaseZindex = false }: EtherscanBtnProps) => {
|
||||
const classes = useStyles()
|
||||
const customClasses = increaseZindex ? { popper: classes.inreasedPopperZindex } : {}
|
||||
|
||||
const EtherscanBtnWithStyles = withStyles(styles)(EtherscanBtn)
|
||||
return (
|
||||
<Tooltip title="Show details on Etherscan" placement="top" classes={customClasses}>
|
||||
<a
|
||||
className={classes.container}
|
||||
href={getEtherScanLink(type, value)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Show details on Etherscan"
|
||||
>
|
||||
<Img src={SearchIcon} height={20} alt="Etherscan" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default EtherscanBtnWithStyles
|
||||
export default EtherscanBtn
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Col from '~/components/layout/Col'
|
||||
import {
|
||||
xs, sm, md, border,
|
||||
} from '~/theme/variables'
|
||||
|
||||
export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
flexGrow: 0,
|
||||
padding: `0 ${md}`,
|
||||
},
|
||||
counter: {
|
||||
background: border,
|
||||
padding: `${xs} ${sm}`,
|
||||
borderRadius: '3px',
|
||||
marginLeft: sm,
|
||||
lineHeight: 'normal',
|
||||
},
|
||||
})
|
||||
|
||||
const EarlyAccessLabel = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Col start="xs" middle="xs" className={classes.container}>
|
||||
<Paragraph size="xs" className={classes.counter}>
|
||||
Early access
|
||||
</Paragraph>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
export default EarlyAccessLabel
|
|
@ -16,6 +16,7 @@ import {
|
|||
border, sm, md, headerHeight,
|
||||
} from '~/theme/variables'
|
||||
import Provider from './Provider'
|
||||
import EarlyAccessLabel from './EarlyAccessLabel'
|
||||
import SafeListHeader from './SafeListHeader'
|
||||
|
||||
const logo = require('../assets/gnosis-safe-logo.svg')
|
||||
|
@ -66,6 +67,7 @@ const Layout = openHoc(({
|
|||
<Divider />
|
||||
<SafeListHeader />
|
||||
<Divider />
|
||||
<EarlyAccessLabel />
|
||||
<Spacer />
|
||||
<Provider open={open} toggle={toggle} info={providerInfo}>
|
||||
{(providerRef) => (
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Dot from '@material-ui/icons/FiberManualRecord'
|
||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||
import CopyBtn from '~/components/CopyBtn'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Link from '~/components/layout/Link'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Identicon from '~/components/Identicon'
|
||||
import Hairline from '~/components/layout/Hairline'
|
||||
|
@ -14,11 +14,10 @@ import Row from '~/components/layout/Row'
|
|||
import Block from '~/components/layout/Block'
|
||||
import Spacer from '~/components/Spacer'
|
||||
import {
|
||||
xs, sm, md, lg, background, secondary, warning, connected as connectedBg,
|
||||
xs, sm, md, lg, background, warning, connected as connectedBg,
|
||||
} from '~/theme/variables'
|
||||
import { upperFirst } from '~/utils/css'
|
||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
||||
import CircleDot from '~/components/Header/component/CircleDot'
|
||||
|
||||
const metamaskIcon = require('../../assets/metamask-icon.svg')
|
||||
|
@ -34,11 +33,6 @@ type Props = {
|
|||
onDisconnect: Function,
|
||||
}
|
||||
|
||||
const openIconStyle = {
|
||||
height: '16px',
|
||||
color: secondary,
|
||||
}
|
||||
|
||||
const styles = () => ({
|
||||
container: {
|
||||
padding: `${md} 12px`,
|
||||
|
@ -65,7 +59,7 @@ const styles = () => ({
|
|||
flexGrow: 1,
|
||||
textAlign: 'center',
|
||||
letterSpacing: '-0.5px',
|
||||
fontSize: '12px',
|
||||
marginRight: sm,
|
||||
},
|
||||
labels: {
|
||||
fontSize: '12px',
|
||||
|
@ -118,14 +112,15 @@ const UserDetails = ({
|
|||
<CircleDot keySize={30} circleSize={75} dotSize={25} dotTop={50} dotRight={25} mode="warning" hideDot />
|
||||
)}
|
||||
</Row>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Paragraph className={classes.address} size="xs" noMargin>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph className={classes.address} size="sm" noMargin>
|
||||
{address}
|
||||
</Paragraph>
|
||||
{userAddress && (
|
||||
<Link className={classes.open} to={getEtherScanLink('address', userAddress)} target="_blank">
|
||||
<OpenInNew style={openIconStyle} />
|
||||
</Link>
|
||||
<>
|
||||
<CopyBtn content={userAddress} increaseZindex />
|
||||
<EtherscanBtn type="address" value={userAddress} increaseZindex />
|
||||
</>
|
||||
)}
|
||||
</Block>
|
||||
</Block>
|
||||
|
@ -146,9 +141,11 @@ const UserDetails = ({
|
|||
Wallet
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
{provider === 'safe'
|
||||
? <Img className={classes.logo} src={safeIcon} height={14} alt="Safe client" />
|
||||
: <Img className={classes.logo} src={metamaskIcon} height={14} alt="Metamask client" />}
|
||||
{provider === 'safe' ? (
|
||||
<Img className={classes.logo} src={safeIcon} height={14} alt="Safe client" />
|
||||
) : (
|
||||
<Img className={classes.logo} src={metamaskIcon} height={14} alt="Metamask client" />
|
||||
)}
|
||||
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||
{upperFirst(provider)}
|
||||
</Paragraph>
|
||||
|
|
|
@ -66,7 +66,7 @@ const ScanQRModal = ({
|
|||
<Hairline />
|
||||
<Col layout="column" middle="xs" className={classes.detailsContainer}>
|
||||
{hasWebcam === null ? (
|
||||
<Block align="center" className={classes.loaderContainer}>
|
||||
<Block justify="center" className={classes.loaderContainer}>
|
||||
<CircularProgress />
|
||||
</Block>
|
||||
) : (
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Link from '~/components/layout/Link'
|
||||
import { sm, primary } from '~/theme/variables'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
padding: `${sm} 0`,
|
||||
},
|
||||
link: {
|
||||
color: primary,
|
||||
},
|
||||
})
|
||||
|
||||
const LegalLinks = () => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<Block className={classes.container} justify="space-around">
|
||||
<Link className={classes.link} to="https://safe.gnosis.io/terms-of-use-072018.html" target="_blank">
|
||||
Terms
|
||||
</Link>
|
||||
<Link className={classes.link} to="https://safe.gnosis.io/privacy-policy-052019.html" target="_blank">
|
||||
Privacy
|
||||
</Link>
|
||||
<Link className={classes.link} to="https://safe.gnosis.io/licenses-092019.html" target="_blank">
|
||||
Licenses
|
||||
</Link>
|
||||
<Link className={classes.link} to="https://safe.gnosis.io/imprint.html" target="_blank">
|
||||
Imprint
|
||||
</Link>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
||||
export default LegalLinks
|
|
@ -24,7 +24,7 @@ const DefaultBadge = () => {
|
|||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Block align="left" className={classes.container}>
|
||||
<Block justify="left" className={classes.container}>
|
||||
<Img src={StarIcon} alt="Star Icon" />
|
||||
<Paragraph noMargin size="xs">
|
||||
default
|
||||
|
|
|
@ -14,6 +14,7 @@ import Identicon from '~/components/Identicon'
|
|||
import {
|
||||
mediumFontSize, sm, secondary, primary,
|
||||
} from '~/theme/variables'
|
||||
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||
import { shortVersionOf, sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
|
@ -71,7 +72,11 @@ const SafeList = ({
|
|||
<MuiList className={classes.list}>
|
||||
{safes.map((safe) => (
|
||||
<React.Fragment key={safe.address}>
|
||||
<Link to={`${SAFELIST_ADDRESS}/${safe.address}`} onClick={onSafeClick} data-testid={SIDEBAR_SAFELIST_ROW_TESTID}>
|
||||
<Link
|
||||
to={`${SAFELIST_ADDRESS}/${safe.address}`}
|
||||
onClick={onSafeClick}
|
||||
data-testid={SIDEBAR_SAFELIST_ROW_TESTID}
|
||||
>
|
||||
<ListItem classes={{ root: classes.listItemRoot }}>
|
||||
<ListItemIcon>
|
||||
<Identicon address={safe.address} diameter={32} className={classes.icon} />
|
||||
|
@ -82,9 +87,7 @@ const SafeList = ({
|
|||
classes={{ primary: classes.safeName, secondary: classes.safeAddress }}
|
||||
/>
|
||||
<Paragraph size="lg" color="primary">
|
||||
{safe.ethBalance}
|
||||
{' '}
|
||||
ETH
|
||||
{`${formatAmount(safe.ethBalance)} ETH`}
|
||||
</Paragraph>
|
||||
{sameAddress(defaultSafe, safe.address) ? (
|
||||
<DefaultBadge />
|
||||
|
|
|
@ -12,15 +12,17 @@ import Link from '~/components/layout/Link'
|
|||
import Spacer from '~/components/Spacer'
|
||||
import Hairline from '~/components/layout/Hairline'
|
||||
import Row from '~/components/layout/Row'
|
||||
import { WELCOME_ADDRESS } from '~/routes/routes'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { defaultSafeSelector } from '~/routes/safe/store/selectors'
|
||||
import setDefaultSafe from '~/routes/safe/store/actions/setDefaultSafe'
|
||||
import { sortedSafeListSelector } from './selectors'
|
||||
import useSidebarStyles from './style'
|
||||
import SafeList from './SafeList'
|
||||
import { WELCOME_ADDRESS } from '~/routes/routes'
|
||||
import LegalLinks from './LegalLinks'
|
||||
import useSidebarStyles from './style'
|
||||
|
||||
const { useState, useEffect } = React
|
||||
|
||||
const { useState, useEffect, useMemo } = React
|
||||
|
||||
type TSidebarContext = {
|
||||
isOpen: boolean,
|
||||
|
@ -83,7 +85,7 @@ const Sidebar = ({
|
|||
}
|
||||
}
|
||||
|
||||
const filteredSafes = filterBy(filter, safes)
|
||||
const filteredSafes = useMemo(() => filterBy(filter, safes), [safes, filter])
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={{ isOpen, toggleSidebar }}>
|
||||
|
@ -128,6 +130,7 @@ const Sidebar = ({
|
|||
setDefaultSafe={setDefaultSafeAction}
|
||||
defaultSafe={defaultSafe}
|
||||
/>
|
||||
<LegalLinks />
|
||||
</Drawer>
|
||||
</ClickAwayListener>
|
||||
{children}
|
||||
|
|
|
@ -21,6 +21,7 @@ type Props<K> = {
|
|||
children: Function,
|
||||
size: number,
|
||||
defaultFixed: boolean,
|
||||
defaultRowsPerPage: number,
|
||||
defaultOrder: 'desc' | 'asc',
|
||||
noBorder: boolean,
|
||||
disablePagination: boolean,
|
||||
|
@ -31,7 +32,7 @@ type State = {
|
|||
order?: Order,
|
||||
orderBy?: string,
|
||||
orderProp: boolean,
|
||||
rowsPerPage: number,
|
||||
rowsPerPage?: number,
|
||||
fixed?: boolean,
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
orderBy: undefined,
|
||||
fixed: undefined,
|
||||
orderProp: false,
|
||||
rowsPerPage: 5,
|
||||
rowsPerPage: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,6 +148,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
defaultOrderBy,
|
||||
defaultOrder,
|
||||
defaultFixed,
|
||||
defaultRowsPerPage,
|
||||
noBorder,
|
||||
} = this.props
|
||||
const {
|
||||
|
@ -154,6 +156,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
} = this.state
|
||||
const orderByParam = orderBy || defaultOrderBy
|
||||
const orderParam = order || defaultOrder
|
||||
const displayRows = rowsPerPage || defaultRowsPerPage
|
||||
const fixedParam = typeof fixed !== 'undefined' ? fixed : !!defaultFixed
|
||||
|
||||
const paginationClasses = {
|
||||
|
@ -165,10 +168,10 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
let sortedData = stableSort(data, getSorting(orderParam, orderByParam, orderProp), fixedParam)
|
||||
|
||||
if (!disablePagination) {
|
||||
sortedData = sortedData.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
sortedData = sortedData.slice(page * displayRows, page * displayRows + displayRows)
|
||||
}
|
||||
|
||||
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage)
|
||||
const emptyRows = displayRows - Math.min(displayRows, data.length - page * displayRows)
|
||||
const isEmpty = size === 0
|
||||
|
||||
return (
|
||||
|
@ -191,7 +194,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
<TablePagination
|
||||
component="div"
|
||||
count={size}
|
||||
rowsPerPage={rowsPerPage}
|
||||
rowsPerPage={displayRows}
|
||||
rowsPerPageOptions={[5, 10, 25, 50, 100]}
|
||||
page={page}
|
||||
backIconButtonProps={backProps}
|
||||
|
@ -209,6 +212,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
GnoTable.defaultProps = {
|
||||
defaultOrder: 'asc',
|
||||
disablePagination: false,
|
||||
defaultRowsPerPage: 5,
|
||||
}
|
||||
|
||||
export default withStyles(styles)(GnoTable)
|
||||
|
|
|
@ -12,7 +12,7 @@ const cx = classNames.bind(styles)
|
|||
type Props = {
|
||||
margin?: Size,
|
||||
padding?: Size,
|
||||
align?: 'center' | 'right' | 'left',
|
||||
justify?: 'center' | 'right' | 'left' | 'space-around',
|
||||
children: React.Node,
|
||||
className?: string,
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ type Props = {
|
|||
class Block extends PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
margin, padding, align, children, className, ...props
|
||||
margin, padding, justify, children, className, ...props
|
||||
} = this.props
|
||||
|
||||
const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
|
||||
return (
|
||||
<div className={cx(className, 'block', margin, paddingStyle, align)} {...props}>
|
||||
<div className={cx(className, 'block', margin, paddingStyle, justify)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -52,6 +52,12 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.space-around {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -6,7 +6,11 @@ const fetchTokenList = () => {
|
|||
const apiUrl = getRelayUrl()
|
||||
const url = `${apiUrl}/tokens`
|
||||
|
||||
return axios.get(url)
|
||||
return axios.get(url, {
|
||||
params: {
|
||||
limit: 300,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default fetchTokenList
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// @flow
|
||||
|
||||
// This is pretty new so I'll leave the docs here
|
||||
// https://v8.dev/features/intl-numberformat
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
|
||||
|
||||
// Locale is an empty array because we want it to use user's locale
|
||||
const lt1kFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 5 })
|
||||
const lt10kFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 4 })
|
||||
const lt100kFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 3 })
|
||||
const lt1mFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 2 })
|
||||
const lt10mFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 1 })
|
||||
const lt100mFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 0 })
|
||||
// same format for billions and trillions
|
||||
const lt1000tFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 3, notation: 'compact' })
|
||||
|
||||
export const formatAmount = (number: string | number) => {
|
||||
let numberFloat = parseFloat(number)
|
||||
|
||||
if (numberFloat < 1000) {
|
||||
numberFloat = lt1kFormatter.format(numberFloat)
|
||||
} else if (numberFloat < 10000) {
|
||||
numberFloat = lt10kFormatter.format(numberFloat)
|
||||
} else if (numberFloat < 100000) {
|
||||
numberFloat = lt100kFormatter.format(numberFloat)
|
||||
} else if (numberFloat < 1000000) {
|
||||
numberFloat = lt1mFormatter.format(numberFloat)
|
||||
} else if (numberFloat < 10000000) {
|
||||
numberFloat = lt10mFormatter.format(numberFloat)
|
||||
} else if (numberFloat < 100000000) {
|
||||
numberFloat = lt100mFormatter.format(numberFloat)
|
||||
} else if (numberFloat < 10 ** 15) {
|
||||
numberFloat = lt1000tFormatter.format(numberFloat)
|
||||
} else {
|
||||
numberFloat = '> 1000T'
|
||||
}
|
||||
|
||||
return numberFloat
|
||||
}
|
|
@ -47,7 +47,7 @@ export const getEtherScanLink = (type: 'address' | 'tx', value: string) => {
|
|||
let web3
|
||||
export const getWeb3 = () => web3 || (window.web3 && new Web3(window.web3.currentProvider)) || (window.ethereum && new Web3(window.ethereum))
|
||||
|
||||
const getProviderName: Function = (web3Provider): boolean => {
|
||||
const getProviderName: Function = (web3Provider): string => {
|
||||
let name
|
||||
|
||||
switch (web3Provider.currentProvider.constructor.name) {
|
||||
|
@ -58,7 +58,7 @@ const getProviderName: Function = (web3Provider): boolean => {
|
|||
name = WALLET_PROVIDER.METAMASK
|
||||
break
|
||||
default:
|
||||
name = 'UNKNOWN'
|
||||
name = 'Wallet'
|
||||
}
|
||||
|
||||
return name
|
||||
|
@ -86,10 +86,7 @@ export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
|
|||
if (window.ethereum) {
|
||||
web3Provider = window.ethereum
|
||||
try {
|
||||
const accounts = await web3Provider.enable()
|
||||
if (!accounts) {
|
||||
throw new Error()
|
||||
}
|
||||
await web3Provider.enable()
|
||||
} catch (error) {
|
||||
console.error('Error when enabling web3 provider', error)
|
||||
}
|
||||
|
|
|
@ -135,13 +135,13 @@ const Details = ({ classes, errors, form }: Props) => (
|
|||
<Block margin="sm">
|
||||
<Paragraph noMargin size="md" color="primary" className={classes.links}>
|
||||
By continuing you consent with the
|
||||
{' '}
|
||||
|
||||
<a rel="noopener noreferrer" href="https://safe.gnosis.io/terms" target="_blank">
|
||||
terms of use
|
||||
</a>
|
||||
{' '}
|
||||
|
||||
and
|
||||
{' '}
|
||||
|
||||
<a rel="noopener noreferrer" href="https://safe.gnosis.io/privacy" target="_blank">
|
||||
privacy policy
|
||||
</a>
|
||||
|
|
|
@ -164,7 +164,7 @@ class ReviewComponent extends React.PureComponent<Props, State> {
|
|||
<Paragraph size="lg" noMargin>
|
||||
{values[getOwnerNameBy(index)]}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{address}
|
||||
</Paragraph>
|
||||
|
|
|
@ -19,7 +19,7 @@ type Props = SelectorProps & Actions
|
|||
export const loadSafe = async (
|
||||
safeName: string,
|
||||
safeAddress: string,
|
||||
owners: Array,
|
||||
owners: Array<*>,
|
||||
addSafe: Function,
|
||||
) => {
|
||||
const safeProps = await buildSafe(safeAddress, safeName)
|
||||
|
|
|
@ -144,7 +144,7 @@ const ReviewComponent = ({ values, classes, userAccount }: Props) => {
|
|||
<Paragraph size="lg" noMargin>
|
||||
{name}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{addresses[index]}
|
||||
</Paragraph>
|
||||
|
|
|
@ -16,7 +16,7 @@ const AssetTableCell = (props: Props) => {
|
|||
const { asset } = props
|
||||
|
||||
return (
|
||||
<Block align="left">
|
||||
<Block justify="left">
|
||||
<Img src={asset.logoUri} height={26} alt={asset.name} onError={setImageToPlaceholder} />
|
||||
<Paragraph size="lg" style={{ marginLeft: 10 }} noMargin>{asset.name}</Paragraph>
|
||||
</Block>
|
||||
|
|
|
@ -92,7 +92,7 @@ const Receive = ({
|
|||
<Block className={classes.qrContainer}>
|
||||
<QRCode value={safeAddress} size={135} />
|
||||
</Block>
|
||||
<Block align="center" className={classes.addressContainer}>
|
||||
<Block justify="center" className={classes.addressContainer}>
|
||||
<Identicon address={safeAddress} diameter={32} />
|
||||
<Paragraph
|
||||
onClick={() => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import SelectField from '~/components/forms/SelectField'
|
|||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||
import { required } from '~/components/forms/validator'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||
import { selectedTokenStyles, selectStyles } from './style'
|
||||
|
||||
type SelectFieldProps = {
|
||||
|
@ -35,7 +36,7 @@ const SelectedToken = ({ token, classes }: SelectedTokenProps) => (
|
|||
<ListItemText
|
||||
className={classes.tokenData}
|
||||
primary={token.name}
|
||||
secondary={`${token.balance} ${token.symbol}`}
|
||||
secondary={`${formatAmount(token.balance)} ${token.symbol}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
@ -54,7 +55,7 @@ const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) =
|
|||
const [initialToken, setInitialToken] = useState<InitialTokenType>('')
|
||||
|
||||
useEffect(() => {
|
||||
const selectedToken = tokens.find(token => token.name === initialValue)
|
||||
const selectedToken = tokens.find((token) => token.name === initialValue)
|
||||
setInitialToken(selectedToken || '')
|
||||
}, [initialValue])
|
||||
|
||||
|
@ -64,16 +65,16 @@ const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) =
|
|||
component={SelectField}
|
||||
classes={{ selectMenu: classes.selectMenu }}
|
||||
validate={required}
|
||||
renderValue={token => <SelectedTokenStyled token={token} />}
|
||||
renderValue={(token) => <SelectedTokenStyled token={token} />}
|
||||
initialValue={initialToken}
|
||||
displayEmpty
|
||||
>
|
||||
{tokens.map(token => (
|
||||
{tokens.map((token) => (
|
||||
<MenuItem key={token.address} value={token}>
|
||||
<ListItemIcon>
|
||||
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={token.name} secondary={`${token.balance} ${token.symbol}`} />
|
||||
<ListItemText primary={token.name} secondary={`${formatAmount(token.balance)} ${token.symbol}`} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Field>
|
||||
|
|
|
@ -42,7 +42,7 @@ const Tokens = (props: Props) => {
|
|||
return (
|
||||
<>
|
||||
<Row align="center" grow className={classes.heading}>
|
||||
<Paragraph className={classes.manage} noMargin weight="bolder">
|
||||
<Paragraph size="xl" noMargin weight="bolder">
|
||||
Manage Tokens
|
||||
</Paragraph>
|
||||
<IconButton onClick={onClose} disableRipple data-testid={MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID}>
|
||||
|
|
|
@ -174,7 +174,7 @@ const AddCustomToken = (props: Props) => {
|
|||
className={classes.addressInput}
|
||||
testId={ADD_CUSTOM_TOKEN_DECIMALS_INPUT_TEST_ID}
|
||||
/>
|
||||
<Block align="left">
|
||||
<Block justify="left">
|
||||
<Field name="showForAllSafes" component={Checkbox} type="checkbox" className={classes.checkbox} />
|
||||
<Paragraph weight="bolder" size="md" className={classes.checkboxLabel}>
|
||||
Activate token for all Safes
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
// @flow
|
||||
import React, { memo } from 'react'
|
||||
import { List, Set } from 'immutable'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import Switch from '@material-ui/core/Switch'
|
||||
import Img from '~/components/layout/Img'
|
||||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { styles } from './style'
|
||||
|
||||
export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn'
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
activeTokensAddresses: Set<string>,
|
||||
tokens: List<Token>,
|
||||
onSwitch: Function,
|
||||
},
|
||||
style: Object,
|
||||
index: number,
|
||||
classes: Object,
|
||||
}
|
||||
|
||||
const TokenRow = memo(({
|
||||
data, index, classes, style,
|
||||
}: Props) => {
|
||||
const { tokens, activeTokensAddresses, onSwitch } = data
|
||||
const token: Token = tokens.get(index)
|
||||
const isActive = activeTokensAddresses.has(token.address)
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<ListItem className={classes.token} classes={{ root: classes.tokenRoot }}>
|
||||
<ListItemIcon className={classes.tokenIcon}>
|
||||
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={token.symbol} secondary={token.name} />
|
||||
{token.address !== ETH_ADDRESS && (
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
onChange={onSwitch(token)}
|
||||
checked={isActive}
|
||||
inputProps={{ 'data-testid': `${token.symbol}_${TOGGLE_TOKEN_TEST_ID}` }}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
)}
|
||||
</ListItem>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default withStyles(styles)(TokenRow)
|
|
@ -2,30 +2,23 @@
|
|||
import * as React from 'react'
|
||||
import { List, Set } from 'immutable'
|
||||
import cn from 'classnames'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import SearchBar from 'material-ui-search-bar'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import MuiList from '@material-ui/core/List'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import Switch from '@material-ui/core/Switch'
|
||||
import Search from '@material-ui/icons/Search'
|
||||
import Img from '~/components/layout/Img'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Divider from '~/components/layout/Divider'
|
||||
import Hairline from '~/components/layout/Hairline'
|
||||
import Spacer from '~/components/Spacer'
|
||||
import Row from '~/components/layout/Row'
|
||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||
import TokenRow from './TokenRow'
|
||||
import { styles } from './style'
|
||||
|
||||
export const ADD_CUSTOM_TOKEN_BUTTON_TEST_ID = 'add-custom-token-btn'
|
||||
export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn'
|
||||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
|
@ -50,10 +43,12 @@ const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.fi
|
|||
|
||||
// OPTIMIZATION IDEA (Thanks Andre)
|
||||
// Calculate active tokens on component mount, store it in component state
|
||||
// After user closes modal, dispatch an action so we dont have 100500 actions
|
||||
// And selectors dont recalculate
|
||||
// After user closes modal, dispatch an action so we don't have 100500 actions
|
||||
// And selectors don't recalculate
|
||||
|
||||
class Tokens extends React.Component<Props, State> {
|
||||
renderCount = 0
|
||||
|
||||
state = {
|
||||
filter: '',
|
||||
activeTokensAddresses: Set([]),
|
||||
|
@ -85,7 +80,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
const { activeTokensAddresses } = this.state
|
||||
const { updateActiveTokens, safeAddress } = this.props
|
||||
|
||||
updateActiveTokens(safeAddress, activeTokensAddresses.toList())
|
||||
updateActiveTokens(safeAddress, activeTokensAddresses)
|
||||
}
|
||||
|
||||
onCancelSearch = () => {
|
||||
|
@ -110,6 +105,18 @@ class Tokens extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
createItemData = (tokens, activeTokensAddresses) => ({
|
||||
tokens,
|
||||
activeTokensAddresses,
|
||||
onSwitch: this.onSwitch,
|
||||
})
|
||||
|
||||
getItemKey = (index, { tokens }) => {
|
||||
const token: Token = tokens.get(index)
|
||||
|
||||
return token.address
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, tokens, setActiveScreen } = this.props
|
||||
const { filter, activeTokensAddresses } = this.state
|
||||
|
@ -122,6 +129,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
const switchToAddCustomTokenScreen = () => setActiveScreen('addCustomToken')
|
||||
|
||||
const filteredTokens = filterBy(filter, tokens)
|
||||
const itemData = this.createItemData(filteredTokens, activeTokensAddresses)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -143,42 +151,35 @@ class Tokens extends React.Component<Props, State> {
|
|||
size="small"
|
||||
color="primary"
|
||||
className={classes.add}
|
||||
classes={{ label: classes.addBtnLabel }}
|
||||
onClick={switchToAddCustomTokenScreen}
|
||||
testId={ADD_CUSTOM_TOKEN_BUTTON_TEST_ID}
|
||||
>
|
||||
+ ADD CUSTOM TOKEN
|
||||
+ Add custom token
|
||||
</Button>
|
||||
</Row>
|
||||
<Hairline />
|
||||
</Block>
|
||||
<MuiList className={classes.list}>
|
||||
{!tokens.size && (
|
||||
<Block align="center" className={classes.progressContainer}>
|
||||
<CircularProgress />
|
||||
</Block>
|
||||
)}
|
||||
{filteredTokens.map((token: Token) => {
|
||||
const isActive = activeTokensAddresses.has(token.address)
|
||||
|
||||
return (
|
||||
<ListItem key={token.address} className={classes.token}>
|
||||
<ListItemIcon className={classes.tokenIcon}>
|
||||
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={token.symbol} secondary={token.name} />
|
||||
{token.address !== ETH_ADDRESS && (
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
onChange={this.onSwitch(token)}
|
||||
checked={isActive}
|
||||
inputProps={{ 'data-testid': `${token.symbol}_${TOGGLE_TOKEN_TEST_ID}` }}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
)}
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</MuiList>
|
||||
{!tokens.size && (
|
||||
<Block justify="center" className={classes.progressContainer}>
|
||||
<CircularProgress />
|
||||
</Block>
|
||||
)}
|
||||
{tokens.size > 0 && (
|
||||
<MuiList className={classes.list}>
|
||||
<FixedSizeList
|
||||
height={413}
|
||||
width={500}
|
||||
overscanCount={process.env.NODE_ENV === 'test' ? 100 : 10}
|
||||
itemCount={filteredTokens.size}
|
||||
itemData={itemData}
|
||||
itemSize={51}
|
||||
itemKey={this.getItemKey}
|
||||
>
|
||||
{TokenRow}
|
||||
</FixedSizeList>
|
||||
</MuiList>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
|
||||
export const styles = () => ({
|
||||
root: {
|
||||
minHeight: '48px',
|
||||
minHeight: '52px',
|
||||
},
|
||||
search: {
|
||||
color: secondaryText,
|
||||
|
@ -20,6 +20,9 @@ export const styles = () => ({
|
|||
paddingRight: md,
|
||||
paddingLeft: md,
|
||||
},
|
||||
addBtnLabel: {
|
||||
fontSize: mediumFontSize,
|
||||
},
|
||||
actions: {
|
||||
height: '50px',
|
||||
},
|
||||
|
@ -33,6 +36,10 @@ export const styles = () => ({
|
|||
minHeight: '50px',
|
||||
borderBottom: `1px solid ${border}`,
|
||||
},
|
||||
tokenRoot: {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
searchInput: {
|
||||
backgroundColor: 'transparent',
|
||||
lineHeight: 'initial',
|
||||
|
@ -49,6 +56,8 @@ export const styles = () => ({
|
|||
},
|
||||
tokenIcon: {
|
||||
marginRight: md,
|
||||
height: '28px',
|
||||
width: '28px',
|
||||
},
|
||||
progressContainer: {
|
||||
width: '100%',
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
// @flow
|
||||
import { lg, sm } from '~/theme/variables'
|
||||
import { lg, md } from '~/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
heading: {
|
||||
padding: `${sm} ${lg}`,
|
||||
padding: `${md} ${lg}`,
|
||||
justifyContent: 'space-between',
|
||||
maxHeight: '75px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
manage: {
|
||||
fontSize: '24px',
|
||||
},
|
||||
close: {
|
||||
height: '35px',
|
||||
width: '35px',
|
||||
|
|
|
@ -3,6 +3,7 @@ import { List } from 'immutable'
|
|||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { buildOrderFieldFrom, FIXED, type SortRow } from '~/components/Table/sorting'
|
||||
import { type Column } from '~/components/Table/TableHead'
|
||||
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||
|
||||
export const BALANCE_TABLE_ASSET_ID = 'asset'
|
||||
export const BALANCE_TABLE_BALANCE_ID = 'balance'
|
||||
|
@ -19,7 +20,7 @@ export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
|
|||
const rows = activeTokens.map((token: Token) => ({
|
||||
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri },
|
||||
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
|
||||
[BALANCE_TABLE_BALANCE_ID]: `${token.balance} ${token.symbol}`,
|
||||
[BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`,
|
||||
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
|
||||
[FIXED]: token.get('symbol') === 'ETH',
|
||||
}))
|
||||
|
|
|
@ -155,6 +155,7 @@ class Balances extends React.Component<Props, State> {
|
|||
<Table
|
||||
label="Balances"
|
||||
defaultOrderBy={BALANCE_TABLE_ASSET_ID}
|
||||
defaultRowsPerPage={10}
|
||||
columns={columns}
|
||||
data={filteredData}
|
||||
size={filteredData.size}
|
||||
|
|
|
@ -16,7 +16,8 @@ import Identicon from '~/components/Identicon'
|
|||
import Heading from '~/components/layout/Heading'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Link from '~/components/layout/Link'
|
||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||
import CopyBtn from '~/components/CopyBtn'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Modal from '~/components/Modal'
|
||||
import SendModal from './Balances/SendModal'
|
||||
|
@ -105,13 +106,12 @@ const Layout = (props: Props) => {
|
|||
</Heading>
|
||||
{!granted && <Block className={classes.readonly}>Read Only</Block>}
|
||||
</Row>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" className={classes.address} color="disabled" noMargin>
|
||||
{address}
|
||||
</Paragraph>
|
||||
<Link className={classes.open} to={etherScanLink} target="_blank">
|
||||
<OpenInNew style={openIconStyle} />
|
||||
</Link>
|
||||
<CopyBtn content={address} />
|
||||
<EtherscanBtn type="address" value={address} />
|
||||
</Block>
|
||||
</Block>
|
||||
<Block className={classes.balance}>
|
||||
|
|
|
@ -101,7 +101,7 @@ const ReviewAddOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{owner.name}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{owner.address}
|
||||
</Paragraph>
|
||||
|
@ -130,7 +130,7 @@ const ReviewAddOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{values.ownerName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{values.ownerAddress}
|
||||
</Paragraph>
|
||||
|
|
|
@ -98,7 +98,7 @@ const EditOwnerComponent = ({
|
|||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Identicon address={ownerAddress} diameter={32} />
|
||||
<Paragraph style={{ marginLeft: 10 }} size="md" color="disabled" noMargin>
|
||||
{ownerAddress}
|
||||
|
|
|
@ -11,7 +11,7 @@ type Props = {
|
|||
const OwnerAddressTableCell = (props: Props) => {
|
||||
const { address } = props
|
||||
return (
|
||||
<Block align="left">
|
||||
<Block justify="left">
|
||||
<Identicon address={address} diameter={32} />
|
||||
<Paragraph style={{ marginLeft: 10 }}>{address}</Paragraph>
|
||||
</Block>
|
||||
|
|
|
@ -64,7 +64,7 @@ const CheckOwner = ({
|
|||
<Paragraph size="lg" noMargin weight="bolder">
|
||||
{ownerName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{ownerAddress}
|
||||
</Paragraph>
|
||||
|
|
|
@ -113,7 +113,7 @@ const ReviewRemoveOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{owner.name}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{owner.address}
|
||||
</Paragraph>
|
||||
|
@ -147,7 +147,7 @@ const ReviewRemoveOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{ownerName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{ownerAddress}
|
||||
</Paragraph>
|
||||
|
|
|
@ -95,7 +95,7 @@ const OwnerForm = ({
|
|||
<Paragraph size="lg" noMargin weight="bolder">
|
||||
{ownerName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{ownerAddress}
|
||||
</Paragraph>
|
||||
|
|
|
@ -115,7 +115,7 @@ const ReviewRemoveOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{owner.name}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{owner.address}
|
||||
</Paragraph>
|
||||
|
@ -149,7 +149,7 @@ const ReviewRemoveOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{ownerName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{ownerAddress}
|
||||
</Paragraph>
|
||||
|
@ -175,7 +175,7 @@ const ReviewRemoveOwner = ({
|
|||
<Paragraph weight="bolder" size="lg" noMargin>
|
||||
{values.ownerName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{values.ownerAddress}
|
||||
</Paragraph>
|
||||
|
|
|
@ -58,7 +58,7 @@ const RemoveSafeComponent = ({
|
|||
<Paragraph size="lg" noMargin weight="bolder">
|
||||
{safeName}
|
||||
</Paragraph>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Block justify="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>
|
||||
{safeAddress}
|
||||
</Paragraph>
|
||||
|
|
|
@ -14,6 +14,9 @@ export const styles = () => ({
|
|||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
address: {
|
||||
marginRight: sm,
|
||||
},
|
||||
user: {
|
||||
justifyContent: 'left',
|
||||
},
|
||||
|
|
|
@ -18,7 +18,6 @@ import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
|
|||
import { orderedTokenListSelector, tokensSelector } from '~/logic/tokens/store/selectors'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { type Transaction, type TransactionStatus } from '~/routes/safe/store/models/transaction'
|
||||
import { type TokenBalance } from '~/routes/safe/store/models/tokenBalance'
|
||||
import { safeParamAddressSelector } from '../store/selectors'
|
||||
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
||||
|
||||
|
@ -84,14 +83,14 @@ const extendedSafeTokensSelector: Selector<GlobalState, RouterProps, List<Token>
|
|||
safeBalancesSelector,
|
||||
tokensSelector,
|
||||
safeEthAsTokenSelector,
|
||||
(safeTokens: List<string>, balances: List<TokenBalance>, tokensList: Map<string, Token>, ethAsToken: Token) => {
|
||||
(safeTokens: List<string>, balances: Map<string, string>, tokensList: Map<string, Token>, ethAsToken: Token) => {
|
||||
const extendedTokens = Map().withMutations((map) => {
|
||||
safeTokens.forEach((tokenAddress: string) => {
|
||||
const baseToken = tokensList.get(tokenAddress)
|
||||
const tokenBalance = balances.find((tknBalance) => tknBalance.address === tokenAddress)
|
||||
const tokenBalance = balances.get(tokenAddress)
|
||||
|
||||
if (baseToken) {
|
||||
map.set(tokenAddress, baseToken.set('balance', tokenBalance ? tokenBalance.balance : '0'))
|
||||
map.set(tokenAddress, baseToken.set('balance', tokenBalance || '0'))
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// @flow
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { List } from 'immutable'
|
||||
import { Map, List } from 'immutable'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import TokenBalanceRecord from '~/routes/safe/store/models/tokenBalance'
|
||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import updateSafe from './updateSafe'
|
||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||
|
@ -36,13 +35,20 @@ const fetchTokenBalances = (safeAddress: string, tokens: List<Token>) => async (
|
|||
const withBalances = await Promise.all(
|
||||
tokens.map(async (token) => {
|
||||
const balance = await calculateBalanceOf(token.address, safeAddress, token.decimals)
|
||||
return TokenBalanceRecord({
|
||||
return {
|
||||
address: token.address,
|
||||
balance,
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
dispatch(updateSafe({ address: safeAddress, balances: List(withBalances) }))
|
||||
|
||||
const balances = Map().withMutations((map) => {
|
||||
withBalances.forEach(({ address, balance }) => {
|
||||
map.set(address, balance)
|
||||
})
|
||||
})
|
||||
|
||||
dispatch(updateSafe({ address: safeAddress, balances }))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error when fetching token balances:', err)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { Set } from 'immutable'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store'
|
||||
import updateSafe from './updateSafe'
|
||||
|
@ -14,7 +14,7 @@ import updateSafe from './updateSafe'
|
|||
// },
|
||||
// })
|
||||
|
||||
const updateActiveTokens = (safeAddress: string, activeTokens: List<string>) => async (
|
||||
const updateActiveTokens = (safeAddress: string, activeTokens: Set<string>) => async (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
) => {
|
||||
dispatch(updateSafe({ address: safeAddress, activeTokens }))
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
// @flow
|
||||
import { List, Record } from 'immutable'
|
||||
import {
|
||||
List, Record, Map, Set,
|
||||
} from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||
import TokenBalance from '~/routes/safe/store/models/tokenBalance'
|
||||
|
||||
export type SafeProps = {
|
||||
name: string,
|
||||
address: string,
|
||||
threshold: number,
|
||||
owners: List<Owner>,
|
||||
balances?: List<TokenBalance>,
|
||||
activeTokens?: List<string>,
|
||||
balances: Map<string, string>,
|
||||
activeTokens: Set<string>,
|
||||
ethBalance?: string,
|
||||
}
|
||||
|
||||
|
@ -20,8 +21,8 @@ const SafeRecord: RecordFactory<SafeProps> = Record({
|
|||
threshold: 0,
|
||||
ethBalance: 0,
|
||||
owners: List([]),
|
||||
activeTokens: List([]),
|
||||
balances: List([]),
|
||||
activeTokens: new Set([]),
|
||||
balances: Map({}),
|
||||
})
|
||||
|
||||
export type Safe = RecordOf<SafeProps>
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
// @flow
|
||||
import { Record } from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
|
||||
export type TokenBalanceProps = {
|
||||
address: string,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
const TokenBalanceRecord: RecordFactory<TokenBalanceProps> = Record({
|
||||
address: '',
|
||||
balance: '0',
|
||||
})
|
||||
|
||||
export type TokenBalance = RecordOf<TokenBalanceProps>
|
||||
|
||||
export default TokenBalanceRecord
|
|
@ -1,9 +1,8 @@
|
|||
// @flow
|
||||
import { Map, List } from 'immutable'
|
||||
import { Map, Set } from 'immutable'
|
||||
import { handleActions, type ActionType } from 'redux-actions'
|
||||
import { ADD_SAFE, buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
import SafeRecord, { type SafeProps } from '~/routes/safe/store/models/safe'
|
||||
import TokenBalance from '~/routes/safe/store/models/tokenBalance'
|
||||
import { makeOwner, type OwnerProps } from '~/routes/safe/store/models/owner'
|
||||
import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
|
||||
import { ACTIVATE_TOKEN_FOR_ALL_SAFES } from '~/routes/safe/store/actions/activateTokenForAllSafes'
|
||||
|
@ -22,8 +21,8 @@ export const buildSafe = (storedSafe: SafeProps) => {
|
|||
const names = storedSafe.owners.map((owner: OwnerProps) => owner.name)
|
||||
const addresses = storedSafe.owners.map((owner: OwnerProps) => owner.address)
|
||||
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
|
||||
const activeTokens = List(storedSafe.activeTokens)
|
||||
const balances = storedSafe.balances.map((balance) => TokenBalance(balance))
|
||||
const activeTokens = Set(storedSafe.activeTokens)
|
||||
const balances = Map(storedSafe.balances)
|
||||
|
||||
const safe: SafeProps = {
|
||||
...storedSafe,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import Img from '~/components/layout/Img'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Link from '~/components/layout/Link'
|
||||
import { OPEN_ADDRESS, LOAD_ADDRESS } from '~/routes/routes'
|
||||
import { marginButtonImg } from '~/theme/variables'
|
||||
import { marginButtonImg, secondary } from '~/theme/variables'
|
||||
import styles from './Layout.scss'
|
||||
|
||||
const safe = require('../assets/safe.svg')
|
||||
|
@ -16,6 +17,12 @@ type Props = {
|
|||
provider: string,
|
||||
}
|
||||
|
||||
const openIconStyle = {
|
||||
height: '13px',
|
||||
color: secondary,
|
||||
marginBottom: '-2px',
|
||||
}
|
||||
|
||||
type SafeProps = {
|
||||
provider: string,
|
||||
size?: 'small' | 'medium' | 'large',
|
||||
|
@ -59,9 +66,9 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
|
|||
const Welcome = ({ provider }: Props) => (
|
||||
<Block className={styles.safe}>
|
||||
<Heading tag="h1" weight="bold" align="center" margin="lg">
|
||||
Welcome to the Gnosis
|
||||
Welcome to
|
||||
<br />
|
||||
Safe Team Edition
|
||||
Gnosis Safe For Teams
|
||||
</Heading>
|
||||
<Heading tag="h3" align="center" margin="xl">
|
||||
The Gnosis Safe for Teams is geared towards teams managing shared
|
||||
|
@ -71,6 +78,11 @@ const Welcome = ({ provider }: Props) => (
|
|||
wallet with redesigned smart contracts, cheaper setup and transaction
|
||||
<br />
|
||||
costs as well as an enhanced user experience.
|
||||
{' '}
|
||||
<a className={styles.learnMoreLink} href="https://safe.gnosis.io/teams" target="_blank" rel="noopener noreferrer">
|
||||
Learn more
|
||||
<OpenInNew style={openIconStyle} />
|
||||
</a>
|
||||
</Heading>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<CreateSafe size="large" provider={provider} />
|
||||
|
|
|
@ -13,3 +13,7 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.learnMoreLink {
|
||||
color: $secondary;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// @flow
|
||||
import { waitForElement } from '@testing-library/react'
|
||||
import { List } from 'immutable'
|
||||
import { Set, Map } from 'immutable'
|
||||
import { aNewStore } from '~/store'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { sendTokenTo, sendEtherTo } from '~/test/utils/tokenMovements'
|
||||
import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
||||
import { dispatchAddTokenToList } from '~/test/utils/transactions/moveTokens.helper'
|
||||
import TokenBalanceRecord from '~/routes/safe/store/models/tokenBalance'
|
||||
import { calculateBalanceOf } from '~/routes/safe/store/actions/fetchTokenBalances'
|
||||
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
|
@ -34,12 +33,11 @@ describe('DOM > Feature > Balances', () => {
|
|||
const safeTokenBalance = await calculateBalanceOf(tokenAddress, safeAddress, 18)
|
||||
expect(safeTokenBalance).toBe(tokensAmount)
|
||||
|
||||
const balanceAsRecord = TokenBalanceRecord({
|
||||
address: tokenAddress,
|
||||
balance: safeTokenBalance,
|
||||
const balances = Map({
|
||||
[tokenAddress]: safeTokenBalance,
|
||||
})
|
||||
store.dispatch(updateActiveTokens(safeAddress, List([tokenAddress])))
|
||||
store.dispatch(updateSafe({ address: safeAddress, balances: List([balanceAsRecord]) }))
|
||||
store.dispatch(updateActiveTokens(safeAddress, Set([tokenAddress])))
|
||||
store.dispatch(updateSafe({ address: safeAddress, balances }))
|
||||
await sleep(1000)
|
||||
|
||||
const balanceRows = SafeDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import { fireEvent } from '@testing-library/react'
|
||||
import { List } from 'immutable'
|
||||
import { Map, Set } from 'immutable'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { sendTokenTo, sendEtherTo } from '~/test/utils/tokenMovements'
|
||||
|
@ -8,7 +8,6 @@ import { renderSafeView } from '~/test/builder/safe.dom.utils'
|
|||
import { getWeb3, getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
||||
import { dispatchAddTokenToList } from '~/test/utils/transactions/moveTokens.helper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import TokenBalanceRecord from '~/routes/safe/store/models/tokenBalance'
|
||||
import { calculateBalanceOf } from '~/routes/safe/store/actions/fetchTokenBalances'
|
||||
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
|
@ -78,12 +77,12 @@ describe('DOM > Feature > Sending Funds', () => {
|
|||
const safeTokenBalance = await calculateBalanceOf(tokenAddress, safeAddress, 18)
|
||||
expect(safeTokenBalance).toBe(tokensAmount)
|
||||
|
||||
const balanceAsRecord = TokenBalanceRecord({
|
||||
address: tokenAddress,
|
||||
balance: safeTokenBalance,
|
||||
const balances = Map({
|
||||
[tokenAddress]: safeTokenBalance,
|
||||
})
|
||||
store.dispatch(updateActiveTokens(safeAddress, List([tokenAddress])))
|
||||
store.dispatch(updateSafe({ address: safeAddress, balances: List([balanceAsRecord]) }))
|
||||
|
||||
store.dispatch(updateActiveTokens(safeAddress, Set([tokenAddress])))
|
||||
store.dispatch(updateSafe({ address: safeAddress, balances }))
|
||||
await sleep(1000)
|
||||
|
||||
// Open send funds modal
|
||||
|
|
|
@ -39,17 +39,19 @@ describe('DOM > Feature > Sidebar', () => {
|
|||
fireEvent.click(SafeDom.getByTestId(TOGGLE_SIDEBAR_BTN_TESTID))
|
||||
})
|
||||
|
||||
await sleep(400)
|
||||
|
||||
const safes = SafeDom.getAllByTestId(SIDEBAR_SAFELIST_ROW_TESTID)
|
||||
expect(safes.length).toBe(2)
|
||||
|
||||
expect(safes[1]).toContainElement(SafeDom.getByText('default'))
|
||||
expect(safes[0]).toContainElement(SafeDom.getByText('Make default'))
|
||||
expect(safes[0]).toContainElement(SafeDom.getByText('default'))
|
||||
expect(safes[1]).toContainElement(SafeDom.getByText('Make default'))
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(SafeDom.getByText('Make default'))
|
||||
})
|
||||
|
||||
expect(safes[0]).toContainElement(SafeDom.getByText('default'))
|
||||
expect(safes[1]).toContainElement(SafeDom.getByText('Make default'))
|
||||
expect(safes[1]).toContainElement(SafeDom.getByText('default'))
|
||||
expect(safes[0]).toContainElement(SafeDom.getByText('Make default'))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import { waitForElement } from '@testing-library/react'
|
||||
import { List } from 'immutable'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
|
@ -52,26 +53,25 @@ describe('DOM > Feature > Enable and disable default tokens', () => {
|
|||
|
||||
// WHEN
|
||||
const TokensDom = await renderSafeView(store, safeAddress)
|
||||
await sleep(400)
|
||||
|
||||
// Check if only ETH is enabled
|
||||
let balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
let balanceRows = await waitForElement(() => TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID))
|
||||
expect(balanceRows.length).toBe(1)
|
||||
|
||||
// THEN
|
||||
clickOnManageTokens(TokensDom)
|
||||
toggleToken(TokensDom, 'FTE')
|
||||
toggleToken(TokensDom, 'STE')
|
||||
await toggleToken(TokensDom, 'FTE')
|
||||
await toggleToken(TokensDom, 'STE')
|
||||
closeManageTokensModal(TokensDom)
|
||||
|
||||
// Wait for active tokens to save
|
||||
await sleep(1500)
|
||||
|
||||
// Check if tokens were enabled
|
||||
balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
expect(balanceRows.length).toBe(3)
|
||||
expect(balanceRows[1]).toHaveTextContent('FTE')
|
||||
expect(balanceRows[2]).toHaveTextContent('STE')
|
||||
|
||||
await sleep(1000)
|
||||
|
||||
const tokensFromStorage = await getActiveTokens()
|
||||
|
||||
expect(Object.keys(tokensFromStorage)).toContain(firstErc20Token.address)
|
||||
|
@ -79,9 +79,10 @@ describe('DOM > Feature > Enable and disable default tokens', () => {
|
|||
|
||||
// disable tokens
|
||||
clickOnManageTokens(TokensDom)
|
||||
toggleToken(TokensDom, 'FTE')
|
||||
toggleToken(TokensDom, 'STE')
|
||||
await toggleToken(TokensDom, 'FTE')
|
||||
await toggleToken(TokensDom, 'STE')
|
||||
closeManageTokensModal(TokensDom)
|
||||
await sleep(1500)
|
||||
|
||||
// check if tokens were disabled
|
||||
balanceRows = TokensDom.getAllByTestId(BALANCE_ROW_TEST_ID)
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
// @flow
|
||||
import { fireEvent } from '@testing-library/react'
|
||||
import { fireEvent, waitForElement, act } from '@testing-library/react'
|
||||
import { MANAGE_TOKENS_BUTTON_TEST_ID } from '~/routes/safe/components/Balances'
|
||||
import {
|
||||
ADD_CUSTOM_TOKEN_BUTTON_TEST_ID,
|
||||
TOGGLE_TOKEN_TEST_ID,
|
||||
} from '~/routes/safe/components/Balances/Tokens/screens/TokenList'
|
||||
import { ADD_CUSTOM_TOKEN_BUTTON_TEST_ID } from '~/routes/safe/components/Balances/Tokens/screens/TokenList'
|
||||
import { TOGGLE_TOKEN_TEST_ID } from '~/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow'
|
||||
import { MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID } from '~/routes/safe/components/Balances/Tokens'
|
||||
|
||||
export const clickOnManageTokens = (dom: any): void => {
|
||||
const btn = dom.getByTestId(MANAGE_TOKENS_BUTTON_TEST_ID)
|
||||
|
||||
fireEvent.click(btn)
|
||||
act(() => {
|
||||
fireEvent.click(btn)
|
||||
})
|
||||
}
|
||||
|
||||
export const clickOnAddCustomToken = (dom: any): void => {
|
||||
const btn = dom.getByTestId(ADD_CUSTOM_TOKEN_BUTTON_TEST_ID)
|
||||
|
||||
fireEvent.click(btn)
|
||||
act(() => {
|
||||
fireEvent.click(btn)
|
||||
})
|
||||
}
|
||||
|
||||
export const toggleToken = (dom: any, symbol: string): void => {
|
||||
const btn = dom.getByTestId(`${symbol}_${TOGGLE_TOKEN_TEST_ID}`)
|
||||
export const toggleToken = async (dom: any, symbol: string): Promise<void> => {
|
||||
const btn = await waitForElement(() => dom.getByTestId(`${symbol}_${TOGGLE_TOKEN_TEST_ID}`))
|
||||
|
||||
fireEvent.click(btn)
|
||||
act(() => {
|
||||
fireEvent.click(btn)
|
||||
})
|
||||
}
|
||||
|
||||
export const closeManageTokensModal = (dom: any) => {
|
||||
const btn = dom.getByTestId(MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID)
|
||||
|
||||
fireEvent.click(btn)
|
||||
act(() => {
|
||||
fireEvent.click(btn)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -246,7 +246,7 @@ export default createMuiTheme({
|
|||
caption: {
|
||||
fontFamily: 'Averta, monospace',
|
||||
fontSize: mediumFontSize,
|
||||
order: 3,
|
||||
order: 2,
|
||||
color: disabled,
|
||||
},
|
||||
input: {
|
||||
|
@ -254,6 +254,10 @@ export default createMuiTheme({
|
|||
width: '60px',
|
||||
padding: `0 ${md} 0 0`,
|
||||
},
|
||||
select: {
|
||||
paddingRight: 30,
|
||||
minWidth: '20px',
|
||||
},
|
||||
actions: {
|
||||
order: 4,
|
||||
color: disabled,
|
||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -12165,6 +12165,11 @@ memdown@~3.0.0:
|
|||
ltgt "~2.2.0"
|
||||
safe-buffer "~5.1.1"
|
||||
|
||||
"memoize-one@>=3.1.1 <6":
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
||||
|
||||
memoize-one@^5.0.0:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e"
|
||||
|
@ -14845,7 +14850,7 @@ react-redux@7.1.1:
|
|||
prop-types "^15.7.2"
|
||||
react-is "^16.9.0"
|
||||
|
||||
react-router-dom@^5.1.2:
|
||||
react-router-dom@5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"
|
||||
integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==
|
||||
|
@ -14952,6 +14957,14 @@ react-transition-group@^4.3.0:
|
|||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-window@^1.8.5:
|
||||
version "1.8.5"
|
||||
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
|
||||
integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
memoize-one ">=3.1.1 <6"
|
||||
|
||||
react@16.10.2:
|
||||
version "16.10.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.10.2.tgz#a5ede5cdd5c536f745173c8da47bda64797a4cf0"
|
||||
|
|
Loading…
Reference in New Issue