Merge pull request #86 from gnosis/development
Feature: Safe Overview's Balance & Manage Tokens
This commit is contained in:
commit
aa219bec9a
|
@ -1,3 +1,3 @@
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,500" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:300,400,500" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500,700" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
|
@ -72,6 +72,7 @@
|
||||||
"immutable": "^4.0.0-rc.9",
|
"immutable": "^4.0.0-rc.9",
|
||||||
"jest": "^22.4.2",
|
"jest": "^22.4.2",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
|
"material-ui-search-bar": "^1.0.0-beta.13",
|
||||||
"postcss-loader": "^2.1.1",
|
"postcss-loader": "^2.1.1",
|
||||||
"postcss-mixins": "^6.2.0",
|
"postcss-mixins": "^6.2.0",
|
||||||
"postcss-simple-vars": "^4.1.0",
|
"postcss-simple-vars": "^4.1.0",
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,500" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:300,400,500" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500,700" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||||
<title>Multisig Safe</title>
|
<title>Multisig Safe</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80" viewBox="0 0 80 80">
|
|
||||||
<defs>
|
|
||||||
<circle id="a" cx="36.129" cy="36.129" r="36.129"/>
|
|
||||||
</defs>
|
|
||||||
<g fill="none" fill-rule="evenodd">
|
|
||||||
<g>
|
|
||||||
<use fill="#E4E8F1" xlink:href="#a"/>
|
|
||||||
<path fill="#A2A8BA" d="M33.234 36.695l-2.569 15.782h12.58l-2.566-15.782c2.861-1.372 4.848-4.254 4.848-7.603 0-4.662-3.835-8.447-8.57-8.447-4.73 0-8.57 3.785-8.57 8.447-.005 3.352 1.979 6.23 4.847 7.603"/>
|
|
||||||
</g>
|
|
||||||
<g fill-rule="nonzero" transform="translate(38.71 38.71)">
|
|
||||||
<circle cx="20.645" cy="20.645" r="20.645" fill="#FD7890"/>
|
|
||||||
<path fill="#FFF" d="M20.645 30.968l4.301-5.506a7.354 7.354 0 0 0-4.3-1.376 7.354 7.354 0 0 0-4.302 1.376l4.301 5.506zm0-20.645c-4.839 0-9.307 1.536-12.903 4.129l2.15 2.752c2.987-2.156 6.715-3.44 10.753-3.44s7.766 1.284 10.753 3.44l2.15-2.752c-3.596-2.593-8.064-4.13-12.903-4.13zm0 6.881c-3.226 0-6.2 1.021-8.602 2.753l2.15 2.753a10.978 10.978 0 0 1 6.452-2.065c2.425 0 4.66.769 6.452 2.065l2.15-2.753c-2.401-1.732-5.376-2.753-8.602-2.753z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
||||||
<g fill="none" fill-rule="nonzero">
|
|
||||||
<circle cx="8" cy="8" r="8" fill="#FFC05F"/>
|
|
||||||
<path fill="#FFF" d="M8 12l1.667-2.133A2.85 2.85 0 0 0 8 9.333c-.625 0-1.204.2-1.667.534L8 12zm0-8a8.529 8.529 0 0 0-5 1.6l.833 1.067A7.104 7.104 0 0 1 8 5.333c1.565 0 3.01.498 4.167 1.334L13 5.6A8.529 8.529 0 0 0 8 4zm0 2.667a5.68 5.68 0 0 0-3.333 1.066L5.5 8.8C6.194 8.298 7.06 8 8 8s1.806.298 2.5.8l.833-1.067A5.68 5.68 0 0 0 8 6.667z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 542 B |
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
|
||||||
<g fill="none" fill-rule="nonzero">
|
|
||||||
<circle cx="8" cy="8" r="8" fill="#467EE5"/>
|
|
||||||
<path fill="#FFF" d="M8 12l1.667-2.133A2.85 2.85 0 0 0 8 9.333c-.625 0-1.204.2-1.667.534L8 12zm0-8a8.529 8.529 0 0 0-5 1.6l.833 1.067A7.104 7.104 0 0 1 8 5.333c1.565 0 3.01.498 4.167 1.334L13 5.6A8.529 8.529 0 0 0 8 4zm0 2.667a5.68 5.68 0 0 0-3.333 1.066L5.5 8.8C6.194 8.298 7.06 8 8 8s1.806.298 2.5.8l.833-1.067A5.68 5.68 0 0 0 8 6.667z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 542 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="13" viewBox="0 0 7 13">
|
||||||
|
<path fill="#A2A8BA" fill-rule="evenodd" d="M1.878 6.22l-.995 6.115h4.874l-.994-6.116c1.109-.531 1.879-1.648 1.879-2.946C6.642 1.467 5.155 0 3.32 0 1.488 0 0 1.467 0 3.273-.002 4.572.767 5.688 1.878 6.22"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 300 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
|
||||||
|
<path fill="#A2A8BA" fill-rule="nonzero" d="M6.947 6.947H5.79V4.632h1.158v2.315zm0 2.316H5.79V8.105h1.158v1.158zM0 11h12.737L6.368 0 0 11z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 237 B |
|
@ -0,0 +1,89 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Block from '~/components/layout/Block'
|
||||||
|
import Dot from '@material-ui/icons/FiberManualRecord'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
|
import { fancy, border, warning } from '~/theme/variables'
|
||||||
|
|
||||||
|
const key = require('../assets/key.svg')
|
||||||
|
const triangle = require('../assets/triangle.svg')
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
dot: {
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
color: fancy,
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: border,
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
position: 'relative',
|
||||||
|
top: '-2px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type Mode = 'error' | 'warning'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
classes: Object,
|
||||||
|
keySize: number,
|
||||||
|
circleSize: number,
|
||||||
|
dotSize: number,
|
||||||
|
dotTop: number,
|
||||||
|
dotRight: number,
|
||||||
|
mode: Mode,
|
||||||
|
center?: boolean,
|
||||||
|
hideDot?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildKeyStyleFrom = (size: number, center: boolean, dotSize: number) => ({
|
||||||
|
width: `${size}px`,
|
||||||
|
height: `${size}px`,
|
||||||
|
marginLeft: center ? `${dotSize}px` : 'none',
|
||||||
|
borderRadius: `${size}px`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildDotStyleFrom = (size: number, top: number, right: number, mode: Mode) => ({
|
||||||
|
width: `${size}px`,
|
||||||
|
height: `${size}px`,
|
||||||
|
borderRadius: `${size}px`,
|
||||||
|
top: `${top}px`,
|
||||||
|
right: `${right}px`,
|
||||||
|
color: mode === 'error' ? fancy : warning,
|
||||||
|
})
|
||||||
|
|
||||||
|
const KeyRing = ({
|
||||||
|
classes, circleSize, keySize, dotSize, dotTop, dotRight, mode, center = false, hideDot = false,
|
||||||
|
}: Props) => {
|
||||||
|
const keyStyle = buildKeyStyleFrom(circleSize, center, dotSize)
|
||||||
|
const dotStyle = buildDotStyleFrom(dotSize, dotTop, dotRight, mode)
|
||||||
|
const isWarning = mode === 'warning'
|
||||||
|
const img = isWarning ? triangle : key
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Block className={classes.root}>
|
||||||
|
<Block className={classes.key} style={keyStyle}>
|
||||||
|
<Img
|
||||||
|
src={img}
|
||||||
|
height={keySize}
|
||||||
|
width={isWarning ? keySize + 2 : keySize}
|
||||||
|
alt="Status connection"
|
||||||
|
className={isWarning ? classes.warning : undefined}
|
||||||
|
/>
|
||||||
|
</Block>
|
||||||
|
{ !hideDot && <Dot className={classes.dot} style={dotStyle} /> }
|
||||||
|
</Block>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(KeyRing)
|
|
@ -31,9 +31,9 @@ const styles = () => ({
|
||||||
left: '4px',
|
left: '4px',
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
borderBottom: `solid 2px ${border}`,
|
borderBottom: `solid 1px ${border}`,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
height: '52px',
|
height: '53px',
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { storiesOf } from '@storybook/react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import styles from '~/components/layout/PageFrame/index.scss'
|
import styles from '~/components/layout/PageFrame/index.scss'
|
||||||
import Layout from './Layout'
|
import Layout from './Layout'
|
||||||
import ProviderInfo from './ProviderInfo'
|
import ProviderAccesible from './ProviderInfo/ProviderAccesible'
|
||||||
import ProviderDetails from './ProviderInfo/UserDetails'
|
import UserDetails from './ProviderDetails/UserDetails'
|
||||||
import ProviderDisconnected from './ProviderDisconnected'
|
import ProviderDisconnected from './ProviderInfo/ProviderDisconnected'
|
||||||
import ConnectDetails from './ProviderDisconnected/ConnectDetails'
|
import ConnectDetails from './ProviderDetails/ConnectDetails'
|
||||||
|
|
||||||
const FrameDecorator = story => (
|
const FrameDecorator = story => (
|
||||||
<div className={styles.frame}>
|
<div className={styles.frame}>
|
||||||
|
@ -20,8 +20,8 @@ storiesOf('Components /Header', module)
|
||||||
const provider = 'Metamask'
|
const provider = 'Metamask'
|
||||||
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
|
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
|
||||||
const network = 'RINKEBY'
|
const network = 'RINKEBY'
|
||||||
const info = <ProviderInfo provider={provider} network={network} userAddress={userAddress} connected />
|
const info = <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected />
|
||||||
const details = <ProviderDetails provider={provider} network={network} userAddress={userAddress} connected />
|
const details = <UserDetails provider={provider} network={network} userAddress={userAddress} connected />
|
||||||
|
|
||||||
return <Layout providerInfo={info} providerDetails={details} />
|
return <Layout providerInfo={info} providerDetails={details} />
|
||||||
})
|
})
|
||||||
|
@ -35,8 +35,8 @@ storiesOf('Components /Header', module)
|
||||||
const provider = 'Metamask'
|
const provider = 'Metamask'
|
||||||
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
|
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
|
||||||
const network = 'RINKEBY'
|
const network = 'RINKEBY'
|
||||||
const info = <ProviderInfo provider={provider} network={network} userAddress={userAddress} connected={false} />
|
const info = <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected={false} />
|
||||||
const details = (<ProviderDetails
|
const details = (<UserDetails
|
||||||
provider={provider}
|
provider={provider}
|
||||||
network={network}
|
network={network}
|
||||||
userAddress={userAddress}
|
userAddress={userAddress}
|
||||||
|
|
|
@ -3,11 +3,9 @@ import * as React from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Img from '~/components/layout/Img'
|
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import { md, lg } from '~/theme/variables'
|
import { md, lg } from '~/theme/variables'
|
||||||
|
import CircleDot from '~/components/Header/component/CircleDot'
|
||||||
const connectedLogo = require('../../assets/connect-wallet.svg')
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -45,7 +43,7 @@ const ConnectDetails = ({ classes, onConnect }: Props) => (
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
<Row className={classes.logo} margin="lg">
|
<Row className={classes.logo} margin="lg">
|
||||||
<Img className={classes.img} src={connectedLogo} height={75} alt="Connect a Wallet" />
|
<CircleDot keySize={32} circleSize={75} dotSize={25} dotTop={50} dotRight={25} center mode="error" />
|
||||||
</Row>
|
</Row>
|
||||||
<Row className={classes.connect}>
|
<Row className={classes.connect}>
|
||||||
<Button
|
<Button
|
|
@ -1,24 +1,24 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import Bold from '~/components/layout/Bold'
|
import Dot from '@material-ui/icons/FiberManualRecord'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Spacer from '~/components/Spacer'
|
import Spacer from '~/components/Spacer'
|
||||||
import { xs, sm, md, lg, background, secondary } from '~/theme/variables'
|
import { xs, sm, md, lg, background, secondary, warning, connected as connectedBg } from '~/theme/variables'
|
||||||
import { upperFirst } from '~/utils/css'
|
import { upperFirst } from '~/utils/css'
|
||||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||||
import { openAddressInEtherScan } from '~/logic/wallets/getWeb3'
|
import { openAddressInEtherScan } from '~/logic/wallets/getWeb3'
|
||||||
|
import CircleDot from '~/components/Header/component/CircleDot'
|
||||||
|
|
||||||
const metamask = require('../../assets/metamask.svg')
|
const metamask = require('../../assets/metamask.svg')
|
||||||
const connectedLogo = require('../../assets/connected.svg')
|
|
||||||
const connectedWarning = require('../../assets/connected-error.svg')
|
|
||||||
const dot = require('../../assets/dotRinkeby.svg')
|
const dot = require('../../assets/dotRinkeby.svg')
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -49,10 +49,11 @@ const styles = () => ({
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
backgroundColor: background,
|
backgroundColor: background,
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
padding: sm,
|
padding: '9px',
|
||||||
|
lineHeight: 1,
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
padding: `0 ${lg}`,
|
padding: `0 ${md}`,
|
||||||
height: '20px',
|
height: '20px',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
|
@ -60,6 +61,11 @@ const styles = () => ({
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
letterSpacing: '-0.5px',
|
letterSpacing: '-0.5px',
|
||||||
|
fontSize: '12px',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
fontSize: '12px',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
},
|
},
|
||||||
open: {
|
open: {
|
||||||
paddingLeft: sm,
|
paddingLeft: sm,
|
||||||
|
@ -77,6 +83,17 @@ const styles = () => ({
|
||||||
logo: {
|
logo: {
|
||||||
margin: `0px ${xs}`,
|
margin: `0px ${xs}`,
|
||||||
},
|
},
|
||||||
|
dot: {
|
||||||
|
marginRight: xs,
|
||||||
|
height: '15px',
|
||||||
|
width: '15px',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
color: warning,
|
||||||
|
},
|
||||||
|
connected: {
|
||||||
|
color: connectedBg,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const UserDetails = ({
|
const UserDetails = ({
|
||||||
|
@ -85,14 +102,16 @@ const UserDetails = ({
|
||||||
const status = connected ? 'Connected' : 'Connection error'
|
const status = connected ? 'Connected' : 'Connection error'
|
||||||
const address = userAddress ? shortVersionOf(userAddress, 6) : 'Address not available'
|
const address = userAddress ? shortVersionOf(userAddress, 6) : 'Address not available'
|
||||||
const identiconAddress = userAddress || 'random'
|
const identiconAddress = userAddress || 'random'
|
||||||
const connectionLogo = connected ? connectedLogo : connectedWarning
|
|
||||||
const color = connected ? 'primary' : 'warning'
|
const color = connected ? 'primary' : 'warning'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Block className={classes.container}>
|
<Block className={classes.container}>
|
||||||
<Row className={classes.identicon} margin="md" align="center">
|
<Row className={classes.identicon} margin="md" align="center">
|
||||||
<Identicon address={identiconAddress} diameter={60} />
|
{ connected
|
||||||
|
? <Identicon address={identiconAddress} diameter={60} />
|
||||||
|
: <CircleDot keySize={30} circleSize={75} dotSize={25} dotTop={50} dotRight={25} mode="warning" hideDot />
|
||||||
|
}
|
||||||
</Row>
|
</Row>
|
||||||
<Block align="center" className={classes.user}>
|
<Block align="center" className={classes.user}>
|
||||||
<Paragraph className={classes.address} size="sm" noMargin>{address}</Paragraph>
|
<Paragraph className={classes.address} size="sm" noMargin>{address}</Paragraph>
|
||||||
|
@ -107,33 +126,29 @@ const UserDetails = ({
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline margin="xs" />
|
<Hairline margin="xs" />
|
||||||
<Row className={classes.details}>
|
<Row className={classes.details}>
|
||||||
<Paragraph size="sm" noMargin align="right">Status </Paragraph>
|
<Paragraph noMargin align="right" className={classes.labels}>Status </Paragraph>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Img className={classes.logo} src={connectionLogo} height={16} alt="Conection Status" />
|
<Dot className={classNames(classes.dot, connected ? classes.connected : classes.warning)} />
|
||||||
<Paragraph size="sm" noMargin align="right" color={color}>
|
<Paragraph noMargin align="right" color={color} weight="bolder" className={classes.labels}>
|
||||||
<Bold>
|
|
||||||
{status}
|
{status}
|
||||||
</Bold>
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline margin="xs" />
|
<Hairline margin="xs" />
|
||||||
<Row className={classes.details}>
|
<Row className={classes.details}>
|
||||||
<Paragraph size="sm" noMargin align="right">Client </Paragraph>
|
<Paragraph noMargin align="right" className={classes.labels}>Client </Paragraph>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Img className={classes.logo} src={metamask} height={16} alt="Metamask client" />
|
<Img className={classes.logo} src={metamask} height={14} alt="Metamask client" />
|
||||||
<Paragraph size="sm" noMargin align="right">
|
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||||
<Bold>
|
|
||||||
{upperFirst(provider)}
|
{upperFirst(provider)}
|
||||||
</Bold>
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline margin="xs" />
|
<Hairline margin="xs" />
|
||||||
<Row className={classes.details}>
|
<Row className={classes.details}>
|
||||||
<Paragraph size="sm" noMargin align="right">Network </Paragraph>
|
<Paragraph noMargin align="right" className={classes.labels}>Network </Paragraph>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Img className={classes.logo} src={dot} height={16} alt="Network" />
|
<Img className={classes.logo} src={dot} height={14} alt="Network" />
|
||||||
<Paragraph size="sm" noMargin align="right">
|
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||||
<Bold>{upperFirst(network)}</Bold>
|
{upperFirst(network)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline margin="xs" />
|
<Hairline margin="xs" />
|
||||||
|
@ -145,7 +160,7 @@ const UserDetails = ({
|
||||||
color="primary"
|
color="primary"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<Paragraph className={classes.disconnectText} size="sm" weight="regular" color="white" noMargin>DISCONNECT</Paragraph>
|
<Paragraph className={classes.disconnectText} size="sm" weight="bold" color="white" noMargin>DISCONNECT</Paragraph>
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
|
@ -3,13 +3,11 @@ import * as React from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Img from '~/components/layout/Img'
|
import Dot from '@material-ui/icons/FiberManualRecord'
|
||||||
import { sm } from '~/theme/variables'
|
import { connected as connectedBg, sm } from '~/theme/variables'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||||
|
import CircleDot from '~/components/Header/component/CircleDot'
|
||||||
const connectedLogo = require('../../assets/connected.svg')
|
|
||||||
const connectedWarning = require('../../assets/connected-error.svg')
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
provider: string,
|
provider: string,
|
||||||
|
@ -24,15 +22,21 @@ const styles = () => ({
|
||||||
fontFamily: 'Montserrat, sans-serif',
|
fontFamily: 'Montserrat, sans-serif',
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
top: '10px',
|
height: '15px',
|
||||||
|
width: '15px',
|
||||||
|
top: '12px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
right: '13px',
|
right: '10px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderRadius: '15px',
|
||||||
|
color: connectedBg,
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
paddingRight: sm,
|
paddingRight: sm,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
justifyContent: 'left',
|
||||||
|
alignItems: 'start',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
},
|
},
|
||||||
address: {
|
address: {
|
||||||
|
@ -46,15 +50,21 @@ const ProviderInfo = ({
|
||||||
const providerText = `${provider} [${network}]`
|
const providerText = `${provider} [${network}]`
|
||||||
const cutAddress = connected ? shortVersionOf(userAddress, 6) : 'Connection Error'
|
const cutAddress = connected ? shortVersionOf(userAddress, 6) : 'Connection Error'
|
||||||
const color = connected ? 'primary' : 'warning'
|
const color = connected ? 'primary' : 'warning'
|
||||||
const logo = connected ? connectedLogo : connectedWarning
|
|
||||||
const identiconAddress = userAddress || 'random'
|
const identiconAddress = userAddress || 'random'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{ connected &&
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Identicon address={identiconAddress} diameter={30} />
|
<Identicon address={identiconAddress} diameter={30} />
|
||||||
<Img className={classes.logo} src={logo} height={20} alt="Connection status" />
|
<Dot className={classes.logo} />
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
{ !connected &&
|
||||||
|
<CircleDot keySize={14} circleSize={35} dotSize={16} dotTop={24} dotRight={11} mode="warning" />
|
||||||
|
}
|
||||||
<Col start="sm" layout="column" className={classes.account}>
|
<Col start="sm" layout="column" className={classes.account}>
|
||||||
<Paragraph size="sm" transform="capitalize" className={classes.network} noMargin weight="bold">{providerText}</Paragraph>
|
<Paragraph size="sm" transform="capitalize" className={classes.network} noMargin weight="bolder">{providerText}</Paragraph>
|
||||||
<Paragraph size="sm" className={classes.address} noMargin color={color}>{cutAddress}</Paragraph>
|
<Paragraph size="sm" className={classes.address} noMargin color={color}>{cutAddress}</Paragraph>
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
|
@ -3,11 +3,9 @@ import * as React from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Img from '~/components/layout/Img'
|
|
||||||
import { type Open } from '~/components/hoc/OpenHoc'
|
import { type Open } from '~/components/hoc/OpenHoc'
|
||||||
import { md } from '~/theme/variables'
|
import { sm } from '~/theme/variables'
|
||||||
|
import CircleDot from '~/components/Header/component/CircleDot'
|
||||||
const connectWallet = require('../../assets/connect-wallet.svg')
|
|
||||||
|
|
||||||
type Props = Open & {
|
type Props = Open & {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -19,10 +17,11 @@ const styles = () => ({
|
||||||
fontFamily: 'Montserrat, sans-serif',
|
fontFamily: 'Montserrat, sans-serif',
|
||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
padding: `0 ${md}`,
|
paddingRight: sm,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
alignItems: 'start',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
},
|
},
|
||||||
connect: {
|
connect: {
|
||||||
|
@ -32,7 +31,7 @@ const styles = () => ({
|
||||||
|
|
||||||
const ProviderDesconnected = ({ classes }: Props) => (
|
const ProviderDesconnected = ({ classes }: Props) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Img src={connectWallet} height={35} alt="Status connected" />
|
<CircleDot keySize={17} circleSize={35} dotSize={16} dotTop={24} dotRight={11} mode="error" />
|
||||||
<Col end="sm" middle="xs" layout="column" className={classes.account}>
|
<Col end="sm" middle="xs" layout="column" className={classes.account}>
|
||||||
<Paragraph size="sm" transform="capitalize" className={classes.network} noMargin weight="bold">Not Connected</Paragraph>
|
<Paragraph size="sm" transform="capitalize" className={classes.network} noMargin weight="bold">Not Connected</Paragraph>
|
||||||
<Paragraph size="sm" color="fancy" className={classes.connect} noMargin>Connect Wallet</Paragraph>
|
<Paragraph size="sm" color="fancy" className={classes.connect} noMargin>Connect Wallet</Paragraph>
|
|
@ -4,10 +4,10 @@ import { connect } from 'react-redux'
|
||||||
import { logComponentStack, type Info } from '~/utils/logBoundaries'
|
import { logComponentStack, type Info } from '~/utils/logBoundaries'
|
||||||
import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar/Context'
|
import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar/Context'
|
||||||
import { WALLET_ERROR_MSG } from '~/logic/wallets/store/actions'
|
import { WALLET_ERROR_MSG } from '~/logic/wallets/store/actions'
|
||||||
import ProviderInfo from './component/ProviderInfo'
|
import ProviderAccesible from './component/ProviderInfo/ProviderAccesible'
|
||||||
import ProviderDetails from './component/ProviderInfo/UserDetails'
|
import UserDetails from './component/ProviderDetails/UserDetails'
|
||||||
import ProviderDisconnected from './component/ProviderDisconnected'
|
import ProviderDisconnected from './component/ProviderInfo/ProviderDisconnected'
|
||||||
import ConnectDetails from './component/ProviderDisconnected/ConnectDetails'
|
import ConnectDetails from './component/ProviderDetails/ConnectDetails'
|
||||||
import Layout from './component/Layout'
|
import Layout from './component/Layout'
|
||||||
import actions, { type Actions } from './actions'
|
import actions, { type Actions } from './actions'
|
||||||
import selector, { type SelectorProps } from './selector'
|
import selector, { type SelectorProps } from './selector'
|
||||||
|
@ -54,7 +54,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
return <ProviderDisconnected />
|
return <ProviderDisconnected />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ProviderInfo provider={provider} network={network} userAddress={userAddress} connected={available} />
|
return <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected={available} />
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderDetailsBased = () => {
|
getProviderDetailsBased = () => {
|
||||||
|
@ -67,7 +67,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
return <ConnectDetails onConnect={this.onConnect} />
|
return <ConnectDetails onConnect={this.onConnect} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<ProviderDetails
|
return (<UserDetails
|
||||||
provider={provider}
|
provider={provider}
|
||||||
network={network}
|
network={network}
|
||||||
userAddress={userAddress}
|
userAddress={userAddress}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import Modal from '@material-ui/core/Modal'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
open: boolean,
|
||||||
|
handleClose: Function,
|
||||||
|
children: React$Node,
|
||||||
|
classes: Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
root: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
paper: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '120px',
|
||||||
|
width: '500px',
|
||||||
|
height: '530px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
boxShadow: '0 0 5px 0 rgba(74, 85, 121, 0.5)',
|
||||||
|
'&:focus': {
|
||||||
|
outline: 'none',
|
||||||
|
},
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const GnoModal = ({
|
||||||
|
title, description, open, children, handleClose, classes,
|
||||||
|
}: Props) => (
|
||||||
|
<Modal
|
||||||
|
aria-labelledby={title}
|
||||||
|
aria-describedby={description}
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
className={classes.root}
|
||||||
|
>
|
||||||
|
<div className={classes.paper}>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default withStyles(styles)(GnoModal)
|
|
@ -10,7 +10,7 @@ import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import WarningIcon from '@material-ui/icons/Warning'
|
import WarningIcon from '@material-ui/icons/Warning'
|
||||||
import { type WithStyles } from '~/theme/mui'
|
import { type WithStyles } from '~/theme/mui'
|
||||||
import { secondary } from '~/theme/variables'
|
import { secondary, warning, connected } from '~/theme/variables'
|
||||||
|
|
||||||
type Variant = 'success' | 'error' | 'warning' | 'info'
|
type Variant = 'success' | 'error' | 'warning' | 'info'
|
||||||
|
|
||||||
|
@ -39,13 +39,13 @@ const styles = theme => ({
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
},
|
},
|
||||||
successIcon: {
|
successIcon: {
|
||||||
color: '#00c4c4',
|
color: connected,
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
backgroundColor: '#fff3e2',
|
backgroundColor: '#fff3e2',
|
||||||
},
|
},
|
||||||
warningIcon: {
|
warningIcon: {
|
||||||
color: '#ffc05f',
|
color: warning,
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
backgroundColor: '#ffe6ea',
|
backgroundColor: '#ffe6ea',
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as React from 'react'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import { sm } from '~/theme/variables'
|
import { sm, boldFont } from '~/theme/variables'
|
||||||
|
|
||||||
const controlStyle = {
|
const controlStyle = {
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
|
@ -12,6 +12,11 @@ const controlStyle = {
|
||||||
|
|
||||||
const firstButtonStyle = {
|
const firstButtonStyle = {
|
||||||
marginRight: sm,
|
marginRight: sm,
|
||||||
|
fontWeight: boldFont,
|
||||||
|
}
|
||||||
|
|
||||||
|
const secondButtonStyle = {
|
||||||
|
fontWeight: boldFont,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -41,6 +46,7 @@ const Controls = ({
|
||||||
{back}
|
{back}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
style={secondButtonStyle}
|
||||||
size="small"
|
size="small"
|
||||||
variant="raised"
|
variant="raised"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
|
@ -9,6 +9,7 @@ const styles = () => ({
|
||||||
root: {
|
root: {
|
||||||
margin: '10px',
|
margin: '10px',
|
||||||
maxWidth: '870px',
|
maxWidth: '870px',
|
||||||
|
boxShadow: '0 0 10px 0 rgba(33,48,77,0.10)',
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
letterSpacing: '-0.5px',
|
letterSpacing: '-0.5px',
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { List } from 'immutable'
|
||||||
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
|
import TableHead from '@material-ui/core/TableHead'
|
||||||
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
|
import TableSortLabel from '@material-ui/core/TableSortLabel'
|
||||||
|
import { type Order } from '~/components/Table/sorting'
|
||||||
|
|
||||||
|
export type Column = {
|
||||||
|
id: string,
|
||||||
|
numeric: boolean,
|
||||||
|
order: boolean, // If data for sorting will be provided in a different attr
|
||||||
|
disablePadding: boolean,
|
||||||
|
label: string,
|
||||||
|
custom: boolean, // If content will be rendered by user manually
|
||||||
|
width?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cellWidth = (width: number | typeof undefined) => {
|
||||||
|
if (!width) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: `${width}px`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
columns: List<Column>,
|
||||||
|
orderBy: string, // id of one of the described columns
|
||||||
|
order: Order,
|
||||||
|
onSort: (property: string, orderAttr: boolean) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
class GnoTableHead extends React.PureComponent<Props> {
|
||||||
|
changeSort = (property: string, orderAttr: boolean) => () => {
|
||||||
|
this.props.onSort(property, orderAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { columns, order, orderBy } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((column: Column) => (
|
||||||
|
<TableCell
|
||||||
|
key={column.id}
|
||||||
|
numeric={column.numeric}
|
||||||
|
padding={column.disablePadding ? 'none' : 'default'}
|
||||||
|
sortDirection={orderBy === column.id ? order : false}
|
||||||
|
>
|
||||||
|
<TableSortLabel
|
||||||
|
active={orderBy === column.id}
|
||||||
|
direction={order}
|
||||||
|
onClick={this.changeSort(column.id, column.order)}
|
||||||
|
>
|
||||||
|
{column.label}
|
||||||
|
</TableSortLabel>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GnoTableHead
|
|
@ -0,0 +1,169 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { List } from 'immutable'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import Table from '@material-ui/core/Table'
|
||||||
|
import TableBody from '@material-ui/core/TableBody'
|
||||||
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
|
import TablePagination from '@material-ui/core/TablePagination'
|
||||||
|
import { type Order, stableSort, getSorting } from '~/components/Table/sorting'
|
||||||
|
import TableHead, { type Column } from '~/components/Table/TableHead'
|
||||||
|
import { xl } from '~/theme/variables'
|
||||||
|
|
||||||
|
type Props<K> = {
|
||||||
|
label: string,
|
||||||
|
defaultOrderBy: string,
|
||||||
|
columns: List<Column>,
|
||||||
|
data: Array<K>,
|
||||||
|
classes: Object,
|
||||||
|
children: Function,
|
||||||
|
size: number,
|
||||||
|
defaultFixed?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
page: number,
|
||||||
|
order: Order,
|
||||||
|
orderBy: string,
|
||||||
|
orderProp: boolean,
|
||||||
|
rowsPerPage: number,
|
||||||
|
fixed: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
root: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
boxShadow: '0 -1px 4px 0 rgba(74, 85, 121, 0.5)',
|
||||||
|
},
|
||||||
|
selectRoot: {
|
||||||
|
lineHeight: '40px',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
white: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
paginationRoot: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
boxShadow: '0 2px 4px 0 rgba(74, 85, 121, 0.5)',
|
||||||
|
marginBottom: xl,
|
||||||
|
},
|
||||||
|
loader: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIXED_HEIGHT = 49
|
||||||
|
|
||||||
|
class GnoTable<K> extends React.Component<Props<K>, State> {
|
||||||
|
state = {
|
||||||
|
page: 0,
|
||||||
|
order: 'asc',
|
||||||
|
orderBy: this.props.defaultOrderBy,
|
||||||
|
fixed: !!this.props.defaultFixed,
|
||||||
|
orderProp: false,
|
||||||
|
rowsPerPage: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
onSort = (property: string, orderProp: boolean) => {
|
||||||
|
const orderBy = property
|
||||||
|
let order = 'desc'
|
||||||
|
|
||||||
|
if (this.state.orderBy === property && this.state.order === 'desc') {
|
||||||
|
order = 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(() => ({
|
||||||
|
order, orderBy, orderProp, fixed: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmptyStyle = (emptyRows: number) => ({
|
||||||
|
height: FIXED_HEIGHT * emptyRows,
|
||||||
|
})
|
||||||
|
|
||||||
|
handleChangePage = (e: SyntheticInputEvent<HTMLInputElement>, page: number) => {
|
||||||
|
this.setState({ page })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeRowsPerPage = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
|
const rowsPerPage = Number(event.target.value)
|
||||||
|
this.setState({ rowsPerPage })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
data, label, columns, classes, children, size,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const {
|
||||||
|
order, orderBy, page, orderProp, rowsPerPage, fixed,
|
||||||
|
} = this.state
|
||||||
|
|
||||||
|
const backProps = {
|
||||||
|
'aria-label': 'Previous Page',
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextProps = {
|
||||||
|
'aria-label': 'Next Page',
|
||||||
|
}
|
||||||
|
|
||||||
|
const paginationClasses = {
|
||||||
|
selectRoot: classes.selectRoot,
|
||||||
|
root: classes.paginationRoot,
|
||||||
|
input: classes.white,
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedData = stableSort(data, getSorting(order, orderBy, orderProp), fixed)
|
||||||
|
.slice(page * rowsPerPage, ((page * rowsPerPage) + rowsPerPage))
|
||||||
|
|
||||||
|
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - (page * rowsPerPage))
|
||||||
|
const isEmpty = size === 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{ !isEmpty &&
|
||||||
|
<Table aria-labelledby={label} className={classes.root}>
|
||||||
|
<TableHead
|
||||||
|
columns={columns}
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onSort={this.onSort}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{ children(sortedData) }
|
||||||
|
{ emptyRows > 0 &&
|
||||||
|
<TableRow style={this.getEmptyStyle(emptyRows)}>
|
||||||
|
<TableCell colSpan={4} />
|
||||||
|
</TableRow>
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
{ isEmpty &&
|
||||||
|
<Row className={classNames(classes.loader, classes.root)} style={this.getEmptyStyle(emptyRows + 1)}>
|
||||||
|
<CircularProgress size={60} />
|
||||||
|
</Row>
|
||||||
|
}
|
||||||
|
<TablePagination
|
||||||
|
component="div"
|
||||||
|
count={size}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
page={page}
|
||||||
|
backIconButtonProps={backProps}
|
||||||
|
nextIconButtonProps={nextProps}
|
||||||
|
onChangePage={this.handleChangePage}
|
||||||
|
onChangeRowsPerPage={this.handleChangeRowsPerPage}
|
||||||
|
classes={paginationClasses}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(GnoTable)
|
|
@ -0,0 +1,49 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export const FIXED = 'fixed'
|
||||||
|
type Fixed = {
|
||||||
|
fixed?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SortRow<T> = T & Fixed
|
||||||
|
|
||||||
|
export const buildOrderFieldFrom = (attr: string) => `${attr}Order`
|
||||||
|
|
||||||
|
|
||||||
|
const desc = (a: Object, b: Object, orderBy: string, orderProp: boolean) => {
|
||||||
|
const order = orderProp ? buildOrderFieldFrom(orderBy) : orderBy
|
||||||
|
|
||||||
|
if (b[order] < a[order]) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if (b[order] > a[order]) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
export const stableSort = <SortRow>(array: Array<SortRow>, cmp: any, fixed: boolean): Array<SortRow> => {
|
||||||
|
const fixedElems: Array<SortRow> = fixed ? array.filter((elem: any) => elem.fixed) : []
|
||||||
|
const data: Array<SortRow> = fixed ? array.filter((elem: any) => !elem[FIXED]) : array
|
||||||
|
const stabilizedThis = data.map((el, index) => [el, index])
|
||||||
|
|
||||||
|
stabilizedThis.sort((a, b) => {
|
||||||
|
const order = cmp(a[0], b[0])
|
||||||
|
if (order !== 0) {
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
|
return a[1] - b[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortedElems: Array<SortRow> = stabilizedThis.map(el => el[0])
|
||||||
|
|
||||||
|
return fixedElems.concat(sortedElems)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Order = 'asc' | 'desc'
|
||||||
|
|
||||||
|
export const getSorting = (order: Order, orderBy: string, orderProp: boolean) =>
|
||||||
|
(order === 'desc' ? (a: any, b: any) => desc(a, b, orderBy, orderProp) : (a: any, b: any) => -desc(a, b, orderBy, orderProp))
|
|
@ -4,7 +4,7 @@ import { border } from '~/theme/variables'
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
border: `solid 1px ${border}`,
|
borderRight: `solid 1px ${border}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Divider = () => (
|
const Divider = () => (
|
||||||
|
|
|
@ -3,19 +3,20 @@ import * as React from 'react'
|
||||||
import { type Size, getSize } from '~/theme/size'
|
import { type Size, getSize } from '~/theme/size'
|
||||||
import { border } from '~/theme/variables'
|
import { border } from '~/theme/variables'
|
||||||
|
|
||||||
const calculateStyleFrom = (margin?: Size) => ({
|
const calculateStyleFrom = (color?: string, margin?: Size) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '1px',
|
height: '1px',
|
||||||
backgroundColor: border,
|
backgroundColor: color || border,
|
||||||
margin: `${getSize(margin)} 0px`,
|
margin: `${getSize(margin)} 0px`,
|
||||||
})
|
})
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
margin?: Size,
|
margin?: Size,
|
||||||
|
color?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hairline = ({ margin }: Props) => {
|
const Hairline = ({ margin, color }: Props) => {
|
||||||
const style = calculateStyleFrom(margin)
|
const style = calculateStyleFrom(color, margin)
|
||||||
|
|
||||||
return <div style={style} />
|
return <div style={style} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4';
|
||||||
type Props = {
|
type Props = {
|
||||||
align?: 'left' | 'center' | 'right',
|
align?: 'left' | 'center' | 'right',
|
||||||
margin?: 'sm' | 'md' | 'lg' | 'xl',
|
margin?: 'sm' | 'md' | 'lg' | 'xl',
|
||||||
|
color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled',
|
||||||
tag: HeadingTag,
|
tag: HeadingTag,
|
||||||
truncate?: boolean,
|
truncate?: boolean,
|
||||||
children: React$Node,
|
children: React$Node,
|
||||||
|
@ -19,7 +20,7 @@ type Props = {
|
||||||
class Heading extends React.PureComponent<Props> {
|
class Heading extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
align, tag, truncate, margin, children, ...props
|
align, tag, truncate, margin, color, children, ...props
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const className = cx(
|
const className = cx(
|
||||||
|
@ -27,6 +28,7 @@ class Heading extends React.PureComponent<Props> {
|
||||||
align,
|
align,
|
||||||
tag,
|
tag,
|
||||||
margin ? capitalize(margin, 'margin') : undefined,
|
margin ? capitalize(margin, 'margin') : undefined,
|
||||||
|
color,
|
||||||
{ truncate },
|
{ truncate },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
.h1 {
|
.h1 {
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
font-weight: 500;
|
font-weight: $bolderFont;
|
||||||
letter-spacing: -1px;
|
letter-spacing: -1px;
|
||||||
font-size: $(fontSizeHeadingLg)px;
|
font-size: $(fontSizeHeadingLg)px;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@
|
||||||
|
|
||||||
.h4 {
|
.h4 {
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
letter-spacing: -0.5px;
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
font-family: 'Roboto Mono', monospace;
|
||||||
font-size: $(fontSizeHeadingXs)px;
|
font-size: $(fontSizeHeadingXs)px;
|
||||||
}
|
}
|
||||||
|
@ -63,3 +62,39 @@
|
||||||
.marginXl {
|
.marginXl {
|
||||||
margin: 0 0 $xl 0;
|
margin: 0 0 $xl 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.soft {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium {
|
||||||
|
color: #686868;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancy {
|
||||||
|
color: $fancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: $warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
color: $fontColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
color: $secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: $disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
|
@ -10,12 +10,9 @@ import PageFrame from '~/components/layout/PageFrame'
|
||||||
import { history, store } from '~/store'
|
import { history, store } from '~/store'
|
||||||
import theme from '~/theme/mui'
|
import theme from '~/theme/mui'
|
||||||
import AppRoutes from '~/routes'
|
import AppRoutes from '~/routes'
|
||||||
import fetchSafes from '~/routes/safe/store/actions/fetchSafes'
|
|
||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
store.dispatch(fetchSafes())
|
|
||||||
|
|
||||||
const Root = () => (
|
const Root = () => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<MuiThemeProvider theme={theme}>
|
<MuiThemeProvider theme={theme}>
|
||||||
|
|
|
@ -15,6 +15,9 @@ body {
|
||||||
font-size: $mediumFontSize;
|
font-size: $mediumFontSize;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: $background;
|
background-color: $background;
|
||||||
|
text-rendering: geometricPrecision;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
body>div:first-child {
|
body>div:first-child {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg height="15" viewBox="0 0 12 15" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m703.53289 23.5644311.452749 9.0623595c0 1.0249793.847907 1.8557283 1.894311 1.8557283h6.257594c1.046419 0 1.894311-.8307646 1.894311-1.8557283l.435591-9.0623595zm11.464047-.6426824c.001838-.382297.003063-.4080344.003063-.4337561 0-.9961836-.589984-1.5432721-1.381539-1.5432721l-2.203756.0024506c0-.5232018-.510952-.9471711-1.033543-.9471711h-2.743441c-.523202 0-1.050701.4239536-1.050701.9471711l-2.205481-.0024506c-.874256 0-1.381539.6665691-1.381539 1.5432721 0 .0257311.000613.0514638.003063.4337561h11.994636zm-4.101047 3.0240279c0-.2891812.234036-.5232019.52259-.5232019.289181 0 .523202.2340364.523202.5232019v5.9923776c0 .2891811-.234036.5232018-.523202.5232018-.288554 0-.52259-.2340363-.52259-.5232018zm-2.418783 0c0-.2891812.234036-.5232019.523202-.5232019.288554 0 .52259.2340364.52259.5232019v5.9923776c0 .2891811-.234036.5232018-.52259.5232018-.289181 0-.523202-.2340363-.523202-.5232018zm-2.417528 0c0-.2891812.234648-.5232019.523201-.5232019.289182 0 .523202.2340364.523202.5232019v5.9923776c0 .2891811-.234036.5232018-.523202.5232018-.288553 0-.523201-.2340363-.523201-.5232018z" fill="#a2a8ba" transform="translate(-703 -20)"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
import { getNamesFrom, getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
import { getNamesFrom, getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
@ -41,6 +42,8 @@ const styles = () => ({
|
||||||
name: {
|
name: {
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
userName: {
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
},
|
},
|
||||||
owner: {
|
owner: {
|
||||||
|
@ -115,7 +118,7 @@ const ReviewComponent = ({ values, classes, network }: Props) => {
|
||||||
<Identicon address={addresses[index]} diameter={32} />
|
<Identicon address={addresses[index]} diameter={32} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={11}>
|
<Col xs={11}>
|
||||||
<Block className={classes.name}>
|
<Block className={classNames(classes.name, classes.userName)}>
|
||||||
<Paragraph size="lg" noMargin >{name}</Paragraph>
|
<Paragraph size="lg" noMargin >{name}</Paragraph>
|
||||||
<Block align="center" className={classes.user}>
|
<Block align="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>{addresses[index]}</Paragraph>
|
<Paragraph size="md" color="disabled" noMargin>{addresses[index]}</Paragraph>
|
||||||
|
|
|
@ -36,7 +36,7 @@ const styles = () => ({
|
||||||
const SafeName = ({ classes }: Props) => (
|
const SafeName = ({ classes }: Props) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Block margin="lg">
|
<Block margin="lg">
|
||||||
<Paragraph noMargin size="md" color="primary" weight="light" className={classes.links}>
|
<Paragraph noMargin size="md" color="primary" className={classes.links}>
|
||||||
This setup will create a Safe with one or more owners. Optionally give the Safe a local name.
|
This setup will create a Safe with one or more owners. Optionally give the Safe a local name.
|
||||||
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>.
|
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>.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
|
@ -7,9 +7,8 @@ import { required, composeValidators, uniqueAddress, mustBeEthereumAddress } fro
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
|
||||||
import Delete from '@material-ui/icons/Delete'
|
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||||
import CheckCircle from '@material-ui/icons/CheckCircle'
|
import CheckCircle from '@material-ui/icons/CheckCircle'
|
||||||
import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
||||||
|
@ -19,6 +18,8 @@ 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'
|
||||||
|
|
||||||
|
const trash = require('../../assets/trash.svg')
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
otherAccounts: string[],
|
otherAccounts: string[],
|
||||||
|
@ -57,6 +58,14 @@ const styles = () => ({
|
||||||
color: '#03AE60',
|
color: '#03AE60',
|
||||||
height: '20px',
|
height: '20px',
|
||||||
},
|
},
|
||||||
|
remove: {
|
||||||
|
height: '56px',
|
||||||
|
marginTop: '12px',
|
||||||
|
maxWidth: '50px',
|
||||||
|
'&:hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const getAddressValidators = (addresses: string[], position: number) => {
|
const getAddressValidators = (addresses: string[], position: number) => {
|
||||||
|
@ -115,7 +124,7 @@ class SafeOwners extends React.Component<Props, State> {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Block className={classes.title}>
|
<Block className={classes.title}>
|
||||||
<Paragraph noMargin size="md" color="primary" weight="light">
|
<Paragraph noMargin size="md" color="primary">
|
||||||
Specify the owners of the Safe.
|
Specify the owners of the Safe.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -159,11 +168,9 @@ class SafeOwners extends React.Component<Props, State> {
|
||||||
text="Owner Address"
|
text="Owner Address"
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={1} center="xs" middle="xs">
|
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
|
||||||
{ index > 0 &&
|
{ index > 0 &&
|
||||||
<IconButton aria-label="Delete" onClick={this.onRemoveRow(index)} className={classes.trash}>
|
<Img src={trash} height={20} alt="Delete" onClick={this.onRemoveRow(index)} />
|
||||||
<Delete />
|
|
||||||
</IconButton>
|
|
||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -172,7 +179,7 @@ class SafeOwners extends React.Component<Props, State> {
|
||||||
</Block>
|
</Block>
|
||||||
<Row align="center" grow className={classes.add} margin="xl">
|
<Row align="center" grow className={classes.add} margin="xl">
|
||||||
<Button color="secondary" onClick={this.onAddOwner}>
|
<Button color="secondary" onClick={this.onAddOwner}>
|
||||||
{ADD_OWNER_BUTTON}
|
<Paragraph weight="bold" size="md" noMargin>{ADD_OWNER_BUTTON}</Paragraph>
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Close from '@material-ui/icons/Close'
|
||||||
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import { lg, md } from '~/theme/variables'
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
heading: {
|
||||||
|
padding: `${md} ${lg}`,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
manage: {
|
||||||
|
fontSize: '24px',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
height: '35px',
|
||||||
|
width: '35px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void,
|
||||||
|
classes: Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Send = ({ classes, onClose }: Props) => (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row align="center" grow className={classes.heading}>
|
||||||
|
<Paragraph className={classes.manage} noMargin>Receive Funds</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.close} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default withStyles(styles)(Send)
|
|
@ -0,0 +1,40 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Close from '@material-ui/icons/Close'
|
||||||
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import { lg, md } from '~/theme/variables'
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
heading: {
|
||||||
|
padding: `${md} ${lg}`,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
manage: {
|
||||||
|
fontSize: '24px',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
height: '35px',
|
||||||
|
width: '35px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void,
|
||||||
|
classes: Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Send = ({ classes, onClose }: Props) => (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row align="center" grow className={classes.heading}>
|
||||||
|
<Paragraph className={classes.manage} noMargin>Send Funds</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.close} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default withStyles(styles)(Send)
|
|
@ -0,0 +1,13 @@
|
||||||
|
// @flow
|
||||||
|
import enableToken from '~/routes/tokens/store/actions/enableToken'
|
||||||
|
import disableToken from '~/routes/tokens/store/actions/disableToken'
|
||||||
|
|
||||||
|
export type Actions = {
|
||||||
|
enableToken: typeof enableToken,
|
||||||
|
disableToken: typeof disableToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
enableToken,
|
||||||
|
disableToken,
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { List } from 'immutable'
|
||||||
|
import classNames from 'classnames/bind'
|
||||||
|
import SearchBar from 'material-ui-search-bar'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import MuiList from '@material-ui/core/List'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
|
import Block from '~/components/layout/Block'
|
||||||
|
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 Close from '@material-ui/icons/Close'
|
||||||
|
import Search from '@material-ui/icons/Search'
|
||||||
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Button from '~/components/layout/Button'
|
||||||
|
import Switch from '@material-ui/core/Switch'
|
||||||
|
import Divider from '~/components/layout/Divider'
|
||||||
|
import Hairline from '~/components/layout/Hairline'
|
||||||
|
import Spacer from '~/components/Spacer'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import { type Token } from '~/routes/tokens/store/model/token'
|
||||||
|
import actions, { type Actions } from './actions'
|
||||||
|
import { styles } from './style'
|
||||||
|
|
||||||
|
type Props = Actions & {
|
||||||
|
onClose: () => void,
|
||||||
|
classes: Object,
|
||||||
|
tokens: List<Token>,
|
||||||
|
safeAddress: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
filter: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterBy = (filter: string, tokens: List<Token>): List<Token> =>
|
||||||
|
tokens.filter((token: Token) => !filter ||
|
||||||
|
token.get('symbol').toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
token.get('name').toLowerCase().includes(filter.toLowerCase()))
|
||||||
|
|
||||||
|
|
||||||
|
class Tokens extends React.Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
filter: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancelSearch = () => {
|
||||||
|
this.setState(() => ({ filter: '' }))
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeSearchBar = (value) => {
|
||||||
|
this.setState(() => ({ filter: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
onSwitch = (token: Token) => (e: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
|
const { checked } = e.target
|
||||||
|
const { safeAddress, enableToken, disableToken } = this.props
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
enableToken(safeAddress, token)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
disableToken(safeAddress, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onClose, classes, tokens } = this.props
|
||||||
|
const searchClasses = {
|
||||||
|
input: classes.searchInput,
|
||||||
|
root: classes.searchRoot,
|
||||||
|
iconButton: classes.searchIcon,
|
||||||
|
searchContainer: classes.searchContainer,
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredTokens = filterBy(this.state.filter, tokens)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Block className={classes.root}>
|
||||||
|
<Row align="center" grow className={classes.heading}>
|
||||||
|
<Paragraph className={classes.manage} noMargin>Manage Tokens</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.close} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Row align="center" className={classNames(classes.padding, classes.actions)}>
|
||||||
|
<Search className={classes.search} />
|
||||||
|
<SearchBar
|
||||||
|
placeholder="Search by name or symbol"
|
||||||
|
classes={searchClasses}
|
||||||
|
searchIcon={<div />}
|
||||||
|
onChange={this.onChangeSearchBar}
|
||||||
|
onCancelSearch={this.onCancelSearch}
|
||||||
|
/>
|
||||||
|
<Spacer />
|
||||||
|
<Divider />
|
||||||
|
<Spacer />
|
||||||
|
<Button variant="contained" size="small" color="secondary" className={classes.add} disabled>
|
||||||
|
+ ADD CUSTOM TOKEN
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
</Block>
|
||||||
|
<MuiList className={classes.list}>
|
||||||
|
{filteredTokens.map((token: Token) => (
|
||||||
|
<ListItem key={token.get('address')} className={classes.token}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Img src={token.get('logoUrl')} height={28} alt={token.get('name')} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={token.get('symbol')} secondary={token.get('name')} />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Switch
|
||||||
|
onChange={this.onSwitch(token)}
|
||||||
|
checked={token.get('status')}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</MuiList>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TokenComponent = withStyles(styles)(Tokens)
|
||||||
|
|
||||||
|
export default connect(undefined, actions)(TokenComponent)
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// @flow
|
||||||
|
import { lg, md, sm, xs, mediumFontSize, border } from '~/theme/variables'
|
||||||
|
|
||||||
|
export const styles = () => ({
|
||||||
|
root: {
|
||||||
|
minHeight: '132px',
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
padding: `${md} ${lg}`,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
manage: {
|
||||||
|
fontSize: '24px',
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
height: '50px',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
height: '35px',
|
||||||
|
width: '35px',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
color: '#a2a8ba',
|
||||||
|
paddingLeft: sm,
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
padding: `0 ${md}`,
|
||||||
|
},
|
||||||
|
add: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
paddingRight: md,
|
||||||
|
paddingLeft: md,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
overflowY: 'scroll',
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
minHeight: '50px',
|
||||||
|
borderBottom: `1px solid ${border}`,
|
||||||
|
},
|
||||||
|
searchInput: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
lineHeight: 'initial',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
padding: 0,
|
||||||
|
'& > input::placeholder': {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
color: 'black',
|
||||||
|
},
|
||||||
|
'& > input': {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
searchContainer: {
|
||||||
|
width: '180px',
|
||||||
|
marginLeft: xs,
|
||||||
|
marginRight: xs,
|
||||||
|
},
|
||||||
|
searchRoot: {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent !important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,63 @@
|
||||||
|
// @flow
|
||||||
|
import { List } from 'immutable'
|
||||||
|
import { type Token } from '~/routes/tokens/store/model/token'
|
||||||
|
import { buildOrderFieldFrom, FIXED, type SortRow } from '~/components/Table/sorting'
|
||||||
|
import { type Column } from '~/components/Table/TableHead'
|
||||||
|
|
||||||
|
export const BALANCE_TABLE_ASSET_ID = 'asset'
|
||||||
|
export const BALANCE_TABLE_BALANCE_ID = 'balance'
|
||||||
|
export const BALANCE_TABLE_VALUE_ID = 'value'
|
||||||
|
|
||||||
|
type BalanceData = {
|
||||||
|
asset: string,
|
||||||
|
balance: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BalanceRow = SortRow<BalanceData>
|
||||||
|
|
||||||
|
export const getBalanceData = (activeTokens: List<Token>): Array<BalanceRow> => {
|
||||||
|
const rows = activeTokens.map((token: Token) => ({
|
||||||
|
[BALANCE_TABLE_ASSET_ID]: token.get('name'),
|
||||||
|
[BALANCE_TABLE_BALANCE_ID]: `${token.get('funds')} ${token.get('symbol')}`,
|
||||||
|
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.get('funds')),
|
||||||
|
[FIXED]: token.get('symbol') === 'ETH',
|
||||||
|
}))
|
||||||
|
|
||||||
|
return Array.from(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const generateColumns = () => {
|
||||||
|
const assetRow: Column = {
|
||||||
|
id: BALANCE_TABLE_ASSET_ID,
|
||||||
|
order: false,
|
||||||
|
numeric: false,
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'Asset',
|
||||||
|
custom: false,
|
||||||
|
width: 250,
|
||||||
|
}
|
||||||
|
|
||||||
|
const balanceRow: Column = {
|
||||||
|
id: BALANCE_TABLE_BALANCE_ID,
|
||||||
|
order: true,
|
||||||
|
numeric: true,
|
||||||
|
disablePadding: false,
|
||||||
|
label: 'Balance',
|
||||||
|
custom: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions: Column = {
|
||||||
|
id: 'actions',
|
||||||
|
order: false,
|
||||||
|
numeric: false,
|
||||||
|
disablePadding: false,
|
||||||
|
label: '',
|
||||||
|
custom: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return List([assetRow, balanceRow, actions])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const filterByZero = (data: Array<BalanceRow>, hideZero: boolean): Array<BalanceRow> =>
|
||||||
|
data.filter((row: BalanceRow) => (hideZero ? row[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)] !== 0 : true))
|
|
@ -0,0 +1,150 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { List } from 'immutable'
|
||||||
|
import classNames from 'classnames/bind'
|
||||||
|
import { type Token } from '~/routes/tokens/store/model/token'
|
||||||
|
import CallMade from '@material-ui/icons/CallMade'
|
||||||
|
import CallReceived from '@material-ui/icons/CallReceived'
|
||||||
|
import Button from '@material-ui/core/Button'
|
||||||
|
import Checkbox from '@material-ui/core/Checkbox'
|
||||||
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
|
import TableCell from '@material-ui/core/TableCell'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Col from '~/components/layout/Col'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import Modal from '~/components/Modal'
|
||||||
|
import { type Column, cellWidth } from '~/components/Table/TableHead'
|
||||||
|
import Table from '~/components/Table'
|
||||||
|
import { getBalanceData, generateColumns, BALANCE_TABLE_ASSET_ID, type BalanceRow, filterByZero } from './dataFetcher'
|
||||||
|
import Tokens from './Tokens'
|
||||||
|
import Send from './Send'
|
||||||
|
import Receive from './Receive'
|
||||||
|
import { styles } from './style'
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
hideZero: boolean,
|
||||||
|
showToken: boolean,
|
||||||
|
showReceive: boolean,
|
||||||
|
showSend: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
classes: Object,
|
||||||
|
granted: boolean,
|
||||||
|
tokens: List<Token>,
|
||||||
|
activeTokens: List<Token>,
|
||||||
|
safeAddress: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action = 'Token' | 'Send' | 'Receive'
|
||||||
|
|
||||||
|
class Balances extends React.Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
hideZero: false,
|
||||||
|
showToken: false,
|
||||||
|
showSend: false,
|
||||||
|
showReceive: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
onShow = (action: Action) => () => {
|
||||||
|
this.setState(() => ({ [`show${action}`]: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
onHide = (action: Action) => () => {
|
||||||
|
this.setState(() => ({ [`show${action}`]: false }))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
|
const { checked } = e.target
|
||||||
|
|
||||||
|
this.setState(() => ({ hideZero: checked }))
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
hideZero, showToken, showReceive, showSend,
|
||||||
|
} = this.state
|
||||||
|
const {
|
||||||
|
classes, granted, tokens, safeAddress, activeTokens,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const columns = generateColumns()
|
||||||
|
const autoColumns = columns.filter(c => !c.custom)
|
||||||
|
const checkboxClasses = {
|
||||||
|
root: classes.root,
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredData = filterByZero(getBalanceData(activeTokens), hideZero)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row align="center" className={classes.message}>
|
||||||
|
<Col xs={6}>
|
||||||
|
<Checkbox
|
||||||
|
classes={checkboxClasses}
|
||||||
|
checked={hideZero}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
color="secondary"
|
||||||
|
disableRipple
|
||||||
|
/>
|
||||||
|
<Paragraph className={classes.zero}>Hide zero balances</Paragraph>
|
||||||
|
</Col>
|
||||||
|
<Col xs={6} end="sm">
|
||||||
|
<Paragraph noMargin size="md" color="secondary" className={classes.links} onClick={this.onShow('Token')}>
|
||||||
|
Manage Tokens
|
||||||
|
</Paragraph>
|
||||||
|
<Modal
|
||||||
|
title="Manage Tokens"
|
||||||
|
description="Enable and disable tokens to be listed"
|
||||||
|
handleClose={this.onHide('Token')}
|
||||||
|
open={showToken}
|
||||||
|
>
|
||||||
|
<Tokens tokens={tokens} onClose={this.onHide('Token')} safeAddress={safeAddress} />
|
||||||
|
</Modal>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Table
|
||||||
|
label="Balances"
|
||||||
|
defaultOrderBy={BALANCE_TABLE_ASSET_ID}
|
||||||
|
columns={columns}
|
||||||
|
data={filteredData}
|
||||||
|
size={filteredData.length}
|
||||||
|
defaultFixed
|
||||||
|
>
|
||||||
|
{(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => (
|
||||||
|
<TableRow tabIndex={-1} key={index} className={classes.hide}>
|
||||||
|
{ autoColumns.map((column: Column) => (
|
||||||
|
<TableCell key={column.id} style={cellWidth(column.width)} numeric={column.numeric} component="td">
|
||||||
|
{row[column.id]}
|
||||||
|
</TableCell>
|
||||||
|
)) }
|
||||||
|
<TableCell component="td">
|
||||||
|
<Row align="end" className={classes.actions}>
|
||||||
|
{ granted &&
|
||||||
|
<Button variant="contained" size="small" color="secondary" className={classes.send} onClick={this.onShow('Send')}>
|
||||||
|
<CallMade className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
<Button variant="contained" size="small" color="secondary" className={classes.receive} onClick={this.onShow('Receive')}>
|
||||||
|
<CallReceived className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||||
|
Receive
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</Table>
|
||||||
|
<Modal title="Send Tokens" description="Send Tokens Form" handleClose={this.onHide('Send')} open={showSend}>
|
||||||
|
<Send onClose={this.onHide('Send')} />
|
||||||
|
</Modal>
|
||||||
|
<Modal title="Receive Tokens" description="Receive Tokens Form" handleClose={this.onHide('Receive')} open={showReceive}>
|
||||||
|
<Receive onClose={this.onHide('Receive')} />
|
||||||
|
</Modal>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(Balances)
|
|
@ -0,0 +1,51 @@
|
||||||
|
// @flow
|
||||||
|
import { sm, xs } from '~/theme/variables'
|
||||||
|
|
||||||
|
export const styles = (theme: Object) => ({
|
||||||
|
root: {
|
||||||
|
width: '20px',
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
|
zero: {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
margin: `${sm} 0`,
|
||||||
|
},
|
||||||
|
actionIcon: {
|
||||||
|
marginRight: theme.spacing.unit,
|
||||||
|
},
|
||||||
|
iconSmall: {
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#fff3e2',
|
||||||
|
},
|
||||||
|
'&:hover $actions': {
|
||||||
|
visibility: 'initial',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
visibility: 'hidden',
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
minWidth: '0px',
|
||||||
|
marginRight: sm,
|
||||||
|
width: '70px',
|
||||||
|
},
|
||||||
|
receive: {
|
||||||
|
minWidth: '0px',
|
||||||
|
width: '95px',
|
||||||
|
},
|
||||||
|
leftIcon: {
|
||||||
|
marginRight: xs,
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
'&:hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,20 +1,137 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||||
|
import Tabs from '@material-ui/core/Tabs'
|
||||||
|
import Tab from '@material-ui/core/Tab'
|
||||||
|
import Hairline from '~/components/layout/Hairline'
|
||||||
|
import Block from '~/components/layout/Block'
|
||||||
|
import Identicon from '~/components/Identicon'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import Heading from '~/components/layout/Heading'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import NoSafe from '~/components/NoSafe'
|
import NoSafe from '~/components/NoSafe'
|
||||||
import { type SelectorProps } from '~/routes/safe/container/selector'
|
import { type SelectorProps } from '~/routes/safe/container/selector'
|
||||||
import GnoSafe from './Safe'
|
import { openAddressInEtherScan } from '~/logic/wallets/getWeb3'
|
||||||
|
import { sm, xs, secondary, smallFontSize } from '~/theme/variables'
|
||||||
|
import Balances from './Balances'
|
||||||
|
|
||||||
type Props = SelectorProps
|
type Props = SelectorProps & {
|
||||||
|
classes: Object,
|
||||||
|
granted: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
const Layout = ({
|
type State = {
|
||||||
safe, activeTokens, provider, userAddress,
|
value: number,
|
||||||
}: Props) => (
|
}
|
||||||
<React.Fragment>
|
|
||||||
{ safe
|
const openIconStyle = {
|
||||||
? <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
|
height: '16px',
|
||||||
: <NoSafe provider={provider} text="Not found safe" />
|
color: secondary,
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
marginLeft: sm,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
justifyContent: 'left',
|
||||||
|
},
|
||||||
|
open: {
|
||||||
|
paddingLeft: sm,
|
||||||
|
width: 'auto',
|
||||||
|
'&:hover': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
fontSize: smallFontSize,
|
||||||
|
letterSpacing: '0.5px',
|
||||||
|
color: '#ffffff',
|
||||||
|
backgroundColor: '#a2a8ba',
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: `0 ${sm}`,
|
||||||
|
marginLeft: sm,
|
||||||
|
borderRadius: xs,
|
||||||
|
lineHeight: '28px',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
class Layout extends React.Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
value: 0,
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Layout
|
handleChange = (event, value) => {
|
||||||
|
this.setState({ value })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
safe, provider, network, classes, granted, tokens, activeTokens,
|
||||||
|
} = this.props
|
||||||
|
const { value } = this.state
|
||||||
|
|
||||||
|
if (!safe) {
|
||||||
|
return <NoSafe provider={provider} text="Safe not found" />
|
||||||
|
}
|
||||||
|
// <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
|
||||||
|
const address = safe.get('address')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Block className={classes.container} margin="xl">
|
||||||
|
<Identicon address={address} diameter={50} />
|
||||||
|
<Block className={classes.name}>
|
||||||
|
<Row>
|
||||||
|
<Heading tag="h2" color="secondary">{safe.get('name')}</Heading>
|
||||||
|
{ !granted &&
|
||||||
|
<Block className={classes.readonly} >
|
||||||
|
Read Only
|
||||||
|
</Block>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
<Block align="center" className={classes.user}>
|
||||||
|
<Paragraph size="md" color="disabled" noMargin>{address}</Paragraph>
|
||||||
|
<OpenInNew
|
||||||
|
className={classes.open}
|
||||||
|
style={openIconStyle}
|
||||||
|
onClick={openAddressInEtherScan(address, network)}
|
||||||
|
/>
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
<Row>
|
||||||
|
<Tabs
|
||||||
|
value={value}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
indicatorColor="secondary"
|
||||||
|
textColor="secondary"
|
||||||
|
>
|
||||||
|
<Tab label="Balances" />
|
||||||
|
<Tab label="Transactions" />
|
||||||
|
<Tab label="Settings" />
|
||||||
|
</Tabs>
|
||||||
|
</Row>
|
||||||
|
<Hairline color="#c8ced4" />
|
||||||
|
{value === 0 &&
|
||||||
|
<Balances
|
||||||
|
tokens={tokens}
|
||||||
|
activeTokens={activeTokens}
|
||||||
|
granted={granted}
|
||||||
|
safeAddress={address}
|
||||||
|
/>}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(Layout)
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import Page from '~/components/layout/Page'
|
import Page from '~/components/layout/Page'
|
||||||
import Layout from '~/routes/safe/component/Layout'
|
import Layout from '~/routes/safe/component/Layout'
|
||||||
import NoRights from '~/routes/safe/component/NoRights'
|
|
||||||
import selector, { type SelectorProps } from './selector'
|
import selector, { type SelectorProps } from './selector'
|
||||||
import actions, { type Actions } from './actions'
|
import actions, { type Actions } from './actions'
|
||||||
|
|
||||||
|
@ -15,16 +14,12 @@ const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
|
||||||
|
|
||||||
class SafeView extends React.PureComponent<Props> {
|
class SafeView extends React.PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.props.fetchSafe(this.props.safeUrl)
|
||||||
|
this.props.fetchTokens(this.props.safeUrl)
|
||||||
this.intervalId = setInterval(() => {
|
this.intervalId = setInterval(() => {
|
||||||
const {
|
const { safeUrl, fetchTokens, fetchSafe } = this.props
|
||||||
safe, fetchTokens, fetchSafe,
|
fetchTokens(safeUrl)
|
||||||
} = this.props
|
fetchSafe(safeUrl)
|
||||||
if (!safe) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const safeAddress = safe.get('address')
|
|
||||||
fetchTokens(safeAddress)
|
|
||||||
fetchSafe(safe)
|
|
||||||
}, TIMEOUT)
|
}, TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,15 +42,20 @@ class SafeView extends React.PureComponent<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
safe, provider, activeTokens, granted, userAddress,
|
safe, provider, activeTokens, granted, userAddress, network, tokens,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{ granted
|
<Layout
|
||||||
? <Layout activeTokens={activeTokens} provider={provider} safe={safe} userAddress={userAddress} />
|
activeTokens={activeTokens}
|
||||||
: <NoRights />
|
tokens={tokens}
|
||||||
}
|
provider={provider}
|
||||||
|
safe={safe}
|
||||||
|
userAddress={userAddress}
|
||||||
|
network={network}
|
||||||
|
granted={granted}
|
||||||
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,23 @@
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
||||||
import { safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
import { safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
||||||
import { providerNameSelector, userAccountSelector } from '~/logic/wallets/store/selectors'
|
import { providerNameSelector, userAccountSelector, networkSelector } from '~/logic/wallets/store/selectors'
|
||||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||||
import { activeTokensSelector } from '~/routes/tokens/store/selectors'
|
import { activeTokensSelector, orderedTokenListSelector } from '~/routes/tokens/store/selectors'
|
||||||
import { type Token } from '~/routes/tokens/store/model/token'
|
import { type Token } from '~/routes/tokens/store/model/token'
|
||||||
|
import { safeParamAddressSelector } from '../store/selectors'
|
||||||
|
|
||||||
export type SelectorProps = {
|
export type SelectorProps = {
|
||||||
safe: SafeSelectorProps,
|
safe: SafeSelectorProps,
|
||||||
provider: string,
|
provider: string,
|
||||||
|
tokens: List<Token>,
|
||||||
activeTokens: List<Token>,
|
activeTokens: List<Token>,
|
||||||
userAddress: string,
|
userAddress: string,
|
||||||
|
network: string,
|
||||||
|
safeUrl: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
|
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
|
||||||
|
@ -41,7 +45,10 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
|
||||||
export default createStructuredSelector({
|
export default createStructuredSelector({
|
||||||
safe: safeSelector,
|
safe: safeSelector,
|
||||||
provider: providerNameSelector,
|
provider: providerNameSelector,
|
||||||
|
tokens: orderedTokenListSelector,
|
||||||
activeTokens: activeTokensSelector,
|
activeTokens: activeTokensSelector,
|
||||||
granted: grantedSelector,
|
granted: grantedSelector,
|
||||||
userAddress: userAccountSelector,
|
userAddress: userAccountSelector,
|
||||||
|
network: networkSelector,
|
||||||
|
safeUrl: safeParamAddressSelector,
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { makeOwner, type Owner } from '~/routes/safe/store/model/owner'
|
||||||
|
|
||||||
export const ADD_SAFE = 'ADD_SAFE'
|
export const ADD_SAFE = 'ADD_SAFE'
|
||||||
|
|
||||||
export const buildOwnersFrom = (names: string[], addresses: string[]) => {
|
export const buildOwnersFrom = (names: Array<string>, addresses: Array<string>) => {
|
||||||
const owners = names.map((name: string, index: number) => makeOwner({ name, address: addresses[index] }))
|
const owners = names.map((name: string, index: number) => makeOwner({ name, address: addresses[index] }))
|
||||||
|
|
||||||
return List(owners)
|
return List(owners)
|
||||||
|
|
|
@ -3,10 +3,11 @@ import type { Dispatch as ReduxDispatch } from 'redux'
|
||||||
import { List, Map } from 'immutable'
|
import { List, Map } from 'immutable'
|
||||||
import { type GlobalState } from '~/store/index'
|
import { type GlobalState } from '~/store/index'
|
||||||
import { makeOwner } from '~/routes/safe/store/model/owner'
|
import { makeOwner } from '~/routes/safe/store/model/owner'
|
||||||
import { type SafeProps, type Safe, makeSafe } from '~/routes/safe/store/model/safe'
|
import { type SafeProps, makeSafe } from '~/routes/safe/store/model/safe'
|
||||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||||
import { getOwners } from '~/utils/localStorage'
|
import { getOwners, getSafeName } from '~/utils/localStorage'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeContract } from '~/logic/contracts/safeContracts'
|
||||||
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
const buildOwnersFrom = (safeOwners: string[], storedOwners: Map<string, string>) => (
|
const buildOwnersFrom = (safeOwners: string[], storedOwners: Map<string, string>) => (
|
||||||
safeOwners.map((ownerAddress: string) => {
|
safeOwners.map((ownerAddress: string) => {
|
||||||
|
@ -15,16 +16,17 @@ const buildOwnersFrom = (safeOwners: string[], storedOwners: Map<string, string>
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
export const buildSafe = async (storedSafe: Object) => {
|
export const buildSafe = async (safeAddress: string, safeName: string) => {
|
||||||
const safeAddress = storedSafe.address
|
const web3 = getWeb3()
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||||
|
const gnosisSafe = GnosisSafe.at(safeAddress)
|
||||||
|
|
||||||
const threshold = Number(await gnosisSafe.getThreshold())
|
const threshold = Number(await gnosisSafe.getThreshold())
|
||||||
const owners = List(buildOwnersFrom(await gnosisSafe.getOwners(), getOwners(safeAddress)))
|
const owners = List(buildOwnersFrom(await gnosisSafe.getOwners(), getOwners(safeAddress)))
|
||||||
|
|
||||||
const safe: SafeProps = {
|
const safe: SafeProps = {
|
||||||
address: safeAddress,
|
address: safeAddress,
|
||||||
name: storedSafe.name,
|
name: safeName,
|
||||||
threshold,
|
threshold,
|
||||||
owners,
|
owners,
|
||||||
}
|
}
|
||||||
|
@ -32,9 +34,10 @@ export const buildSafe = async (storedSafe: Object) => {
|
||||||
return makeSafe(safe)
|
return makeSafe(safe)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (safe: Safe) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
try {
|
try {
|
||||||
const safeRecord = await buildSafe(safe.toJSON())
|
const safeName = getSafeName(safeAddress) || 'LOADED SAFE'
|
||||||
|
const safeRecord = await buildSafe(safeAddress, safeName)
|
||||||
|
|
||||||
return dispatch(updateSafe(safeRecord))
|
return dispatch(updateSafe(safeRecord))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
// @flow
|
|
||||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
|
||||||
import { Map } from 'immutable'
|
|
||||||
import { type GlobalState } from '~/store/index'
|
|
||||||
import { load, SAFES_KEY } from '~/utils/localStorage'
|
|
||||||
import updateSafes from '~/routes/safe/store/actions/updateSafes'
|
|
||||||
import { buildSafe } from '~/routes/safe/store/actions/fetchSafe'
|
|
||||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
|
||||||
|
|
||||||
const buildSafesFrom = async (loadedSafes: Object): Promise<Map<string, Safe>> => {
|
|
||||||
const safes = Map()
|
|
||||||
|
|
||||||
const keys = Object.keys(loadedSafes)
|
|
||||||
try {
|
|
||||||
const safeRecords = await Promise.all(keys.map((address: string) => buildSafe(loadedSafes[address])))
|
|
||||||
|
|
||||||
return safes.withMutations(async (map) => {
|
|
||||||
safeRecords.forEach((safe: Safe) => map.set(safe.get('address'), safe))
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log("Error while fetching safes information")
|
|
||||||
|
|
||||||
return Map()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
|
||||||
const storedSafes = load(SAFES_KEY)
|
|
||||||
|
|
||||||
const safes = storedSafes ? await buildSafesFrom(storedSafes) : Map()
|
|
||||||
|
|
||||||
return dispatch(updateSafes(safes))
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
// @flow
|
|
||||||
export * from './addSafe'
|
|
||||||
export { default as addSafe } from './addSafe'
|
|
|
@ -1,33 +1,70 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { Map } from 'immutable'
|
import { Map } from 'immutable'
|
||||||
import { handleActions, type ActionType } from 'redux-actions'
|
import { handleActions, type ActionType } from 'redux-actions'
|
||||||
import addSafe, { ADD_SAFE } from '~/routes/safe/store/actions/addSafe'
|
import addSafe, { ADD_SAFE, buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||||
import { type Safe, makeSafe } from '~/routes/safe/store/model/safe'
|
import { type Safe, type SafeProps, makeSafe } from '~/routes/safe/store/model/safe'
|
||||||
import { saveSafes, setOwners } from '~/utils/localStorage'
|
import { type OwnerProps } from '~/routes/safe/store/model/owner'
|
||||||
import updateSafes, { UPDATE_SAFES } from '~/routes/safe/store/actions/updateSafes'
|
import { saveSafes, setOwners, load, SAFES_KEY } from '~/utils/localStorage'
|
||||||
import updateSafe, { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
|
import updateSafe, { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
|
||||||
|
|
||||||
export const SAFE_REDUCER_ID = 'safes'
|
export const SAFE_REDUCER_ID = 'safes'
|
||||||
|
|
||||||
export type State = Map<string, Safe>
|
export type State = Map<string, Safe>
|
||||||
|
|
||||||
/*
|
export const buildSafe = (storedSafe: SafeProps) => {
|
||||||
type Action<T> = {
|
const names = storedSafe.owners.map((owner: OwnerProps) => owner.name)
|
||||||
key: string,
|
const addresses = storedSafe.owners.map((owner: OwnerProps) => owner.address)
|
||||||
payload: T,
|
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
|
||||||
};
|
|
||||||
|
|
||||||
type AddSafeType = Action<SafeProps>
|
const safe: SafeProps = {
|
||||||
|
address: storedSafe.address,
|
||||||
|
name: storedSafe.name,
|
||||||
|
threshold: storedSafe.threshold,
|
||||||
|
owners,
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeSafe(safe)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildSafesFrom = (loadedSafes: Object): Map<string, Safe> => {
|
||||||
|
const safes: Map<string, Safe> = Map()
|
||||||
|
|
||||||
|
const keys = Object.keys(loadedSafes)
|
||||||
|
try {
|
||||||
|
const safeRecords = keys.map((address: string) => buildSafe(loadedSafes[address]))
|
||||||
|
|
||||||
|
return safes.withMutations(async (map) => {
|
||||||
|
safeRecords.forEach((safe: Safe) => map.set(safe.get('address'), safe))
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.log("Error while fetching safes information")
|
||||||
|
|
||||||
|
return Map()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const safesInitialState = (): State => {
|
||||||
|
const storedSafes = load(SAFES_KEY)
|
||||||
|
const safes = storedSafes ? buildSafesFrom(storedSafes) : Map()
|
||||||
|
|
||||||
|
return safes
|
||||||
|
}
|
||||||
|
|
||||||
action: AddSafeType
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default handleActions({
|
export default handleActions({
|
||||||
[UPDATE_SAFE]: (state: State, action: ActionType<typeof updateSafe>): State =>
|
[UPDATE_SAFE]: (state: State, action: ActionType<typeof updateSafe>): State => {
|
||||||
state.update(action.payload.get('address'), prevSafe =>
|
const safe = action.payload
|
||||||
(prevSafe.equals(action.payload) ? prevSafe : action.payload)),
|
const safeAddress = safe.get('address')
|
||||||
[UPDATE_SAFES]: (state: State, action: ActionType<typeof updateSafes>): State =>
|
|
||||||
action.payload,
|
const hasSafe = !!state.get(safeAddress)
|
||||||
|
if (hasSafe) {
|
||||||
|
return state.update(safeAddress, prevSafe =>
|
||||||
|
(prevSafe.equals(safe) ? prevSafe : safe))
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.set(safeAddress, safe)
|
||||||
|
},
|
||||||
[ADD_SAFE]: (state: State, action: ActionType<typeof addSafe>): State => {
|
[ADD_SAFE]: (state: State, action: ActionType<typeof addSafe>): State => {
|
||||||
const safe: Safe = makeSafe(action.payload)
|
const safe: Safe = makeSafe(action.payload)
|
||||||
setOwners(safe.get('address'), safe.get('owners'))
|
setOwners(safe.get('address'), safe.get('owners'))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
||||||
import { buildOwnersFrom } from '~/routes/safe/store/actions'
|
import { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||||
|
|
||||||
class SafeBuilder {
|
class SafeBuilder {
|
||||||
safe: Safe
|
safe: Safe
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { type Owner } from '~/routes/safe/store/model/owner'
|
||||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||||
|
|
||||||
export const safesMapSelector = (state: GlobalState): Map<string, Safe> => state.safes
|
export const safesMapSelector = (state: GlobalState): Map<string, Safe> => state.safes
|
||||||
|
|
||||||
const safesListSelector: Selector<GlobalState, {}, List<Safe>> = createSelector(
|
const safesListSelector: Selector<GlobalState, {}, List<Safe>> = createSelector(
|
||||||
safesMapSelector,
|
safesMapSelector,
|
||||||
(safes: Map<string, Safe>): List<Safe> => safes.toList(),
|
(safes: Map<string, Safe>): List<Safe> => safes.toList(),
|
||||||
|
|
|
@ -30,6 +30,11 @@ export const activeTokensSelector = createSelector(
|
||||||
(tokens: List<Token>) => tokens.filter((token: Token) => token.get('status')),
|
(tokens: List<Token>) => tokens.filter((token: Token) => token.get('status')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const orderedTokenListSelector = createSelector(
|
||||||
|
tokenListSelector,
|
||||||
|
(tokens: List<Token>) => tokens.sortBy((token: Token) => token.get('symbol')),
|
||||||
|
)
|
||||||
|
|
||||||
export const tokenAddressesSelector = createSelector(
|
export const tokenAddressesSelector = createSelector(
|
||||||
tokenListSelector,
|
tokenListSelector,
|
||||||
(balances: List<Token>) => {
|
(balances: List<Token>) => {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Img from '~/components/layout/Img'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Link from '~/components/layout/Link'
|
import Link from '~/components/layout/Link'
|
||||||
import { OPEN_ADDRESS } from '~/routes/routes'
|
import { OPEN_ADDRESS } from '~/routes/routes'
|
||||||
import { sm } from '~/theme/variables'
|
import { marginButtonImg } from '~/theme/variables'
|
||||||
import styles from './Layout.scss'
|
import styles from './Layout.scss'
|
||||||
|
|
||||||
const safe = require('../assets/safe.svg')
|
const safe = require('../assets/safe.svg')
|
||||||
|
@ -22,7 +22,7 @@ type SafeProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
marginLeft: sm,
|
marginLeft: marginButtonImg,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateSafe = ({ size, provider }: SafeProps) => (
|
export const CreateSafe = ({ size, provider }: SafeProps) => (
|
||||||
|
@ -35,7 +35,7 @@ export const CreateSafe = ({ size, provider }: SafeProps) => (
|
||||||
disabled={!provider}
|
disabled={!provider}
|
||||||
minWidth={240}
|
minWidth={240}
|
||||||
>
|
>
|
||||||
<Img src={plus} height={16} alt="Safe" />
|
<Img src={plus} height={14} alt="Safe" />
|
||||||
<div style={buttonStyle}>Create new Safe</div>
|
<div style={buttonStyle}>Create new Safe</div>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,7 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
|
||||||
disabled={!provider}
|
disabled={!provider}
|
||||||
minWidth={240}
|
minWidth={240}
|
||||||
>
|
>
|
||||||
<Img src={safe} height={16} alt="Safe" />
|
<Img src={safe} height={14} alt="Safe" />
|
||||||
<div style={buttonStyle}>Load existing Safe</div>
|
<div style={buttonStyle}>Load existing Safe</div>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
@ -58,10 +58,8 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
|
||||||
|
|
||||||
const Welcome = ({ provider }: Props) => (
|
const Welcome = ({ provider }: Props) => (
|
||||||
<Block className={styles.safe}>
|
<Block className={styles.safe}>
|
||||||
<Heading tag="h1" align="center">
|
|
||||||
Welcome to the Gnosis
|
|
||||||
</Heading>
|
|
||||||
<Heading tag="h1" align="center" margin="lg">
|
<Heading tag="h1" align="center" margin="lg">
|
||||||
|
Welcome to the Gnosis <br />
|
||||||
Safe Team Edition
|
Safe Team Edition
|
||||||
</Heading>
|
</Heading>
|
||||||
<Heading tag="h4" align="center" margin="xl">
|
<Heading tag="h4" align="center" margin="xl">
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { routerMiddleware, routerReducer } from 'react-router-redux'
|
||||||
import { combineReducers, createStore, applyMiddleware, compose, type Reducer, type Store } from 'redux'
|
import { combineReducers, createStore, applyMiddleware, compose, type Reducer, type Store } from 'redux'
|
||||||
import thunk from 'redux-thunk'
|
import thunk from 'redux-thunk'
|
||||||
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
|
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
|
||||||
import safe, { SAFE_REDUCER_ID, type State as SafeState } from '~/routes/safe/store/reducer/safe'
|
import safe, { SAFE_REDUCER_ID, type State as SafeState, safesInitialState } from '~/routes/safe/store/reducer/safe'
|
||||||
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/routes/tokens/store/reducer/tokens'
|
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/routes/tokens/store/reducer/tokens'
|
||||||
import transactions, { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
|
import transactions, { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ export type GlobalState = {
|
||||||
transactions: TransactionsState,
|
transactions: TransactionsState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GetState = () => GlobalState
|
||||||
|
|
||||||
const reducers: Reducer<GlobalState> = combineReducers({
|
const reducers: Reducer<GlobalState> = combineReducers({
|
||||||
routing: routerReducer,
|
routing: routerReducer,
|
||||||
[PROVIDER_REDUCER_ID]: provider,
|
[PROVIDER_REDUCER_ID]: provider,
|
||||||
|
@ -32,13 +34,11 @@ const reducers: Reducer<GlobalState> = combineReducers({
|
||||||
[TRANSACTIONS_REDUCER_ID]: transactions,
|
[TRANSACTIONS_REDUCER_ID]: transactions,
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
[TRANSACTIONS_REDUCER_ID]: transactionsInitialState(),
|
[SAFE_REDUCER_ID]: safesInitialState(),
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
export const store: Store<GlobalState> = createStore(reducers, {}, finalCreateStore)
|
export const store: Store<GlobalState> = createStore(reducers, initialState, finalCreateStore)
|
||||||
|
|
||||||
export const aNewStore = (localState?: Object): Store<GlobalState> =>
|
export const aNewStore = (localState?: Object): Store<GlobalState> =>
|
||||||
createStore(reducers, localState, finalCreateStore)
|
createStore(reducers, localState, finalCreateStore)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
||||||
import { buildOwnersFrom } from '~/routes/safe/store/actions'
|
import addSafe, { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||||
import { FIELD_NAME, FIELD_CONFIRMATIONS, FIELD_OWNERS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
import { FIELD_NAME, FIELD_CONFIRMATIONS, FIELD_OWNERS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
||||||
import { getWeb3, getProviderInfo } from '~/logic/wallets/getWeb3'
|
import { getWeb3, getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||||
import { promisify } from '~/utils/promisify'
|
import { promisify } from '~/utils/promisify'
|
||||||
import addSafe from '~/routes/safe/store/actions/addSafe'
|
|
||||||
import { createSafe, type OpenState } from '~/routes/open/container/Open'
|
import { createSafe, type OpenState } from '~/routes/open/container/Open'
|
||||||
import { type GlobalState } from '~/store/index'
|
import { type GlobalState } from '~/store/index'
|
||||||
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
||||||
|
|
|
@ -120,7 +120,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
||||||
await assureThresholdIs(gnosisSafe, 1)
|
await assureThresholdIs(gnosisSafe, 1)
|
||||||
await assureOwnersAre(gnosisSafe, accounts[2], accounts[0], accounts[1])
|
await assureOwnersAre(gnosisSafe, accounts[2], accounts[0], accounts[1])
|
||||||
|
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), address)
|
safe = getSafeFrom(store.getState(), address)
|
||||||
expect(safe.get('owners').count()).toBe(3)
|
expect(safe.get('owners').count()).toBe(3)
|
||||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||||
|
@ -149,7 +149,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
||||||
await assureThresholdIs(gnosisSafe, 2)
|
await assureThresholdIs(gnosisSafe, 2)
|
||||||
await assureOwnersAre(gnosisSafe, accounts[2], accounts[0], accounts[1])
|
await assureOwnersAre(gnosisSafe, accounts[2], accounts[0], accounts[1])
|
||||||
|
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), address)
|
safe = getSafeFrom(store.getState(), address)
|
||||||
expect(safe.get('owners').count()).toBe(3)
|
expect(safe.get('owners').count()).toBe(3)
|
||||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||||
|
@ -179,7 +179,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
||||||
await assureThresholdIs(gnosisSafe, 1)
|
await assureThresholdIs(gnosisSafe, 1)
|
||||||
await assureOwnersAre(gnosisSafe, accounts[0])
|
await assureOwnersAre(gnosisSafe, accounts[0])
|
||||||
|
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), address)
|
safe = getSafeFrom(store.getState(), address)
|
||||||
expect(safe.get('owners').count()).toBe(1)
|
expect(safe.get('owners').count()).toBe(1)
|
||||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||||
|
@ -204,7 +204,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
||||||
await assureThresholdIs(gnosisSafe, 1)
|
await assureThresholdIs(gnosisSafe, 1)
|
||||||
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
|
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
|
||||||
|
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), address)
|
safe = getSafeFrom(store.getState(), address)
|
||||||
expect(safe.get('owners').count()).toBe(2)
|
expect(safe.get('owners').count()).toBe(2)
|
||||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||||
|
@ -230,7 +230,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
||||||
await assureThresholdIs(gnosisSafe, 2)
|
await assureThresholdIs(gnosisSafe, 2)
|
||||||
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
|
await assureOwnersAre(gnosisSafe, accounts[0], accounts[1])
|
||||||
|
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), address)
|
safe = getSafeFrom(store.getState(), address)
|
||||||
expect(safe.get('owners').count()).toBe(2)
|
expect(safe.get('owners').count()).toBe(2)
|
||||||
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
await assureOwnersAre(gnosisSafe, ...getAddressesFrom(safe))
|
||||||
|
|
|
@ -36,12 +36,12 @@ describe('Transactions Suite', () => {
|
||||||
const executor = accounts[0]
|
const executor = accounts[0]
|
||||||
const nonce = await gnosisSafe.nonce()
|
const nonce = await gnosisSafe.nonce()
|
||||||
const firstTxHash = await createTransaction(safe, 'Add Owner Second account', safeAddress, 0, nonce, executor, firstTxData)
|
const firstTxHash = await createTransaction(safe, 'Add Owner Second account', safeAddress, 0, nonce, executor, firstTxData)
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), safeAddress)
|
safe = getSafeFrom(store.getState(), safeAddress)
|
||||||
|
|
||||||
const secondTxData = gnosisSafe.contract.addOwnerWithThreshold.getData(accounts[2], 2)
|
const secondTxData = gnosisSafe.contract.addOwnerWithThreshold.getData(accounts[2], 2)
|
||||||
const secondTxHash = await createTransaction(safe, 'Add Owner Third account', safeAddress, 0, nonce + 100, executor, secondTxData)
|
const secondTxHash = await createTransaction(safe, 'Add Owner Third account', safeAddress, 0, nonce + 100, executor, secondTxData)
|
||||||
await store.dispatch(fetchSafe(safe))
|
await store.dispatch(fetchSafe(safe.get('address')))
|
||||||
safe = getSafeFrom(store.getState(), safeAddress)
|
safe = getSafeFrom(store.getState(), safeAddress)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
|
|
117
src/theme/mui.js
117
src/theme/mui.js
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import red from '@material-ui/core/colors/red'
|
|
||||||
import { createMuiTheme } from '@material-ui/core/styles'
|
import { createMuiTheme } from '@material-ui/core/styles'
|
||||||
import { largeFontSize, mediumFontSize, smallFontSize, disabled, primary, secondary, md, lg, background } from './variables'
|
import { largeFontSize, mediumFontSize, smallFontSize, disabled, primary, secondary, md, lg, bolderFont, boldFont, buttonLargeFontSize } from './variables'
|
||||||
|
|
||||||
export type WithStyles = {
|
export type WithStyles = {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -14,7 +13,9 @@ const palette = {
|
||||||
secondary: {
|
secondary: {
|
||||||
main: secondary,
|
main: secondary,
|
||||||
},
|
},
|
||||||
error: red,
|
error: {
|
||||||
|
main: '#FB4F62',
|
||||||
|
},
|
||||||
contrastThreshold: 3,
|
contrastThreshold: 3,
|
||||||
tonalOffset: 0.2,
|
tonalOffset: 0.2,
|
||||||
}
|
}
|
||||||
|
@ -29,7 +30,7 @@ export default createMuiTheme({
|
||||||
MuiButton: {
|
MuiButton: {
|
||||||
root: {
|
root: {
|
||||||
fontFamily: 'Roboto Mono, monospace',
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
letterSpacing: '1px',
|
letterSpacing: '0.9px',
|
||||||
'&:disabled': {
|
'&:disabled': {
|
||||||
color: disabled,
|
color: disabled,
|
||||||
},
|
},
|
||||||
|
@ -38,28 +39,49 @@ export default createMuiTheme({
|
||||||
disabled: {
|
disabled: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
|
contained: {
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
containedPrimary: {
|
containedPrimary: {
|
||||||
backgroundColor: secondary,
|
backgroundColor: secondary,
|
||||||
},
|
},
|
||||||
sizeLarge: {
|
sizeLarge: {
|
||||||
padding: `${md} ${lg}`,
|
padding: `${md} ${lg}`,
|
||||||
minHeight: '52px',
|
minHeight: '52px',
|
||||||
fontSize: mediumFontSize,
|
fontSize: buttonLargeFontSize,
|
||||||
|
fontWeight: boldFont,
|
||||||
},
|
},
|
||||||
sizeSmall: {
|
sizeSmall: {
|
||||||
minWidth: '130px',
|
minWidth: '130px',
|
||||||
fontSize: smallFontSize,
|
fontSize: smallFontSize,
|
||||||
},
|
},
|
||||||
|
textSecondary: {
|
||||||
|
'&:hover': {
|
||||||
|
borderRadius: '3px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiStepper: {
|
||||||
|
root: {
|
||||||
|
padding: '24px 0 0 15px',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
MuiStepIcon: {
|
MuiStepIcon: {
|
||||||
root: {
|
root: {
|
||||||
fontSize: '22px',
|
fontSize: '22px',
|
||||||
|
color: '#A2A8BA !important',
|
||||||
},
|
},
|
||||||
completed: {
|
completed: {
|
||||||
color: `${secondary} !important`,
|
color: `${secondary} !important`,
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
color: `${secondary} !important`,
|
color: `${secondary} !important`,
|
||||||
|
fontWeight: boldFont,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiStepContent: {
|
||||||
|
root: {
|
||||||
|
borderLeft: '1px solid #A2A8BA',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiTypography: {
|
MuiTypography: {
|
||||||
|
@ -72,14 +94,14 @@ export default createMuiTheme({
|
||||||
MuiFormHelperText: {
|
MuiFormHelperText: {
|
||||||
root: {
|
root: {
|
||||||
fontFamily: 'Roboto Mono, monospace',
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
fontSize: smallFontSize,
|
fontSize: '12px',
|
||||||
padding: `0 0 0 ${md}`,
|
padding: `0 0 0 ${md}`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
top: '20px',
|
top: '20px',
|
||||||
color: secondary,
|
color: secondary,
|
||||||
order: 0,
|
order: 0,
|
||||||
marginTop: '0px',
|
marginTop: '0px',
|
||||||
backgroundColor: background,
|
backgroundColor: 'EAE9EF',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiInput: {
|
MuiInput: {
|
||||||
|
@ -90,16 +112,20 @@ export default createMuiTheme({
|
||||||
lineHeight: '56px',
|
lineHeight: '56px',
|
||||||
order: 1,
|
order: 1,
|
||||||
padding: `0 ${md}`,
|
padding: `0 ${md}`,
|
||||||
backgroundColor: background,
|
backgroundColor: '#EAE9EF',
|
||||||
'&:$disabled': {
|
'&:$disabled': {
|
||||||
color: '#0000ff',
|
color: '#0000ff',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
color: 'initial',
|
letterSpacing: '0.5px',
|
||||||
|
color: primary,
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
'&::-webkit-input-placeholder': {
|
||||||
|
color: disabled,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
underline: {
|
underline: {
|
||||||
'&:before': {
|
'&:before': {
|
||||||
|
@ -116,6 +142,10 @@ export default createMuiTheme({
|
||||||
MuiStepLabel: {
|
MuiStepLabel: {
|
||||||
label: {
|
label: {
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
|
color: '#A2A8BA',
|
||||||
|
'&$active': {
|
||||||
|
color: primary,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiSnackbarContent: {
|
MuiSnackbarContent: {
|
||||||
|
@ -125,6 +155,75 @@ export default createMuiTheme({
|
||||||
color: primary,
|
color: primary,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MuiTab: {
|
||||||
|
root: {
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
fontWeight: bolderFont,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTablePagination: {
|
||||||
|
toolbar: {
|
||||||
|
'& > span:nth-child(2)': {
|
||||||
|
order: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
selectIcon: {
|
||||||
|
height: '100%',
|
||||||
|
top: '0px',
|
||||||
|
},
|
||||||
|
caption: {
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
order: 3,
|
||||||
|
color: disabled,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
order: 2,
|
||||||
|
width: '60px',
|
||||||
|
padding: `0 ${md} 0 0`,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
order: 4,
|
||||||
|
color: disabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTableCell: {
|
||||||
|
root: {
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
},
|
||||||
|
head: {
|
||||||
|
letterSpacing: '1px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
fontWeight: boldFont,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
color: primary,
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiBackdrop: {
|
||||||
|
root: {
|
||||||
|
backdropFilter: 'blur(1px)',
|
||||||
|
backgroundColor: 'rgba(228, 232, 241, 0.75)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiListItemText: {
|
||||||
|
primary: {
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
fontWeight: bolderFont,
|
||||||
|
color: primary,
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
fontSize: smallFontSize,
|
||||||
|
color: disabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
palette,
|
palette,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
const border = '#eaebef'
|
const border = '#e4e8f1'
|
||||||
const background = '#f4f4f9'
|
const background = '#f4f4f9'
|
||||||
const primary = '#4a5579'
|
const primary = '#4a5579'
|
||||||
const secondary = '#467ee5' // '#13222b'
|
const secondary = '#467ee5' // '#13222b'
|
||||||
const tertiary = '#f6f9fc'
|
const tertiary = '#f6f9fc'
|
||||||
const fontColor = '#4a5579'
|
const fontColor = '#4a5579'
|
||||||
const fancyColor = '#fd7890'
|
const fancyColor = '#fd7890'
|
||||||
const warningColor = '#c97c05'
|
const warningColor = '#ffc05f'
|
||||||
|
const connectedColor = '#00c4c4'
|
||||||
const disabled = '#65707e'
|
const disabled = '#65707e'
|
||||||
const xs = '4px'
|
const xs = '4px'
|
||||||
const sm = '8px'
|
const sm = '8px'
|
||||||
|
@ -14,6 +15,7 @@ const md = '16px'
|
||||||
const lg = '24px'
|
const lg = '24px'
|
||||||
const xl = '32px'
|
const xl = '32px'
|
||||||
const xxl = '40px'
|
const xxl = '40px'
|
||||||
|
const marginButtonImg = '12px'
|
||||||
|
|
||||||
module.exports = Object.assign({}, {
|
module.exports = Object.assign({}, {
|
||||||
primary,
|
primary,
|
||||||
|
@ -24,6 +26,7 @@ module.exports = Object.assign({}, {
|
||||||
fontColor,
|
fontColor,
|
||||||
fancy: fancyColor,
|
fancy: fancyColor,
|
||||||
warning: warningColor,
|
warning: warningColor,
|
||||||
|
connected: connectedColor,
|
||||||
xs,
|
xs,
|
||||||
sm,
|
sm,
|
||||||
md,
|
md,
|
||||||
|
@ -31,10 +34,12 @@ module.exports = Object.assign({}, {
|
||||||
xl,
|
xl,
|
||||||
xxl,
|
xxl,
|
||||||
border,
|
border,
|
||||||
|
marginButtonImg,
|
||||||
fontSizeHeadingXs: 13,
|
fontSizeHeadingXs: 13,
|
||||||
fontSizeHeadingSm: 18,
|
fontSizeHeadingSm: 18,
|
||||||
fontSizeHeadingMd: 21,
|
fontSizeHeadingMd: 21,
|
||||||
fontSizeHeadingLg: 32,
|
fontSizeHeadingLg: 32,
|
||||||
|
buttonLargeFontSize: '12px',
|
||||||
lightFont: 300,
|
lightFont: 300,
|
||||||
regularFont: 400,
|
regularFont: 400,
|
||||||
bolderFont: 500,
|
bolderFont: 500,
|
||||||
|
|
|
@ -23,6 +23,16 @@ export const load = (key: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getSafeName = (safeAddress: string) => {
|
||||||
|
const safes = load(SAFES_KEY)
|
||||||
|
if (!safes) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const safe = safes[safeAddress]
|
||||||
|
|
||||||
|
return safe ? safe.name : undefined
|
||||||
|
}
|
||||||
|
|
||||||
export const saveSafes = (safes: Object) => {
|
export const saveSafes = (safes: Object) => {
|
||||||
try {
|
try {
|
||||||
const serializedState = JSON.stringify(safes)
|
const serializedState = JSON.stringify(safes)
|
||||||
|
|
|
@ -7975,6 +7975,13 @@ material-colors@^1.2.1:
|
||||||
version "1.2.6"
|
version "1.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
|
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
|
||||||
|
|
||||||
|
material-ui-search-bar@^1.0.0-beta.13:
|
||||||
|
version "1.0.0-beta.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/material-ui-search-bar/-/material-ui-search-bar-1.0.0-beta.13.tgz#08c246431666f91c3ca52df78987b86352783ee1"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
math-expression-evaluator@^1.2.14:
|
math-expression-evaluator@^1.2.14:
|
||||||
version "1.2.17"
|
version "1.2.17"
|
||||||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
||||||
|
|
Loading…
Reference in New Issue