Merge pull request #84 from gnosis/feature/#72-manage-tokens

Feature #72 - Manage tokens
This commit is contained in:
Adolfo Panizo 2018-11-09 09:58:24 +01:00 committed by GitHub
commit 8d9c058ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 602 additions and 332 deletions

View File

@ -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=Roboto+Mono:300,400,500" 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:400,500,700" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

View File

@ -4,8 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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=Roboto+Mono:300,400,500" 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:400,500,700" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<title>Multisig Safe</title>
</head>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -31,9 +31,9 @@ const styles = () => ({
left: '4px',
},
summary: {
borderBottom: `solid 2px ${border}`,
borderBottom: `solid 1px ${border}`,
alignItems: 'center',
height: '52px',
height: '53px',
backgroundColor: 'white',
},
logo: {

View File

@ -3,10 +3,10 @@ import { storiesOf } from '@storybook/react'
import * as React from 'react'
import styles from '~/components/layout/PageFrame/index.scss'
import Layout from './Layout'
import ProviderInfo from './ProviderInfo'
import ProviderDetails from './ProviderInfo/UserDetails'
import ProviderDisconnected from './ProviderDisconnected'
import ConnectDetails from './ProviderDisconnected/ConnectDetails'
import ProviderAccesible from './ProviderInfo/ProviderAccesible'
import UserDetails from './ProviderDetails/UserDetails'
import ProviderDisconnected from './ProviderInfo/ProviderDisconnected'
import ConnectDetails from './ProviderDetails/ConnectDetails'
const FrameDecorator = story => (
<div className={styles.frame}>
@ -20,8 +20,8 @@ storiesOf('Components /Header', module)
const provider = 'Metamask'
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
const network = 'RINKEBY'
const info = <ProviderInfo provider={provider} network={network} userAddress={userAddress} connected />
const details = <ProviderDetails provider={provider} network={network} userAddress={userAddress} connected />
const info = <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected />
const details = <UserDetails provider={provider} network={network} userAddress={userAddress} connected />
return <Layout providerInfo={info} providerDetails={details} />
})
@ -35,8 +35,8 @@ storiesOf('Components /Header', module)
const provider = 'Metamask'
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
const network = 'RINKEBY'
const info = <ProviderInfo provider={provider} network={network} userAddress={userAddress} connected={false} />
const details = (<ProviderDetails
const info = <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected={false} />
const details = (<UserDetails
provider={provider}
network={network}
userAddress={userAddress}

View File

@ -3,11 +3,9 @@ import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import Paragraph from '~/components/layout/Paragraph'
import Button from '~/components/layout/Button'
import Img from '~/components/layout/Img'
import Row from '~/components/layout/Row'
import { md, lg } from '~/theme/variables'
const connectedLogo = require('../../assets/connect-wallet.svg')
import CircleDot from '~/components/Header/component/CircleDot'
type Props = {
classes: Object,
@ -45,7 +43,7 @@ const ConnectDetails = ({ classes, onConnect }: Props) => (
</Row>
</div>
<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 className={classes.connect}>
<Button

View File

@ -1,24 +1,24 @@
// @flow
import * as React from 'react'
import classNames from 'classnames'
import OpenInNew from '@material-ui/icons/OpenInNew'
import { withStyles } from '@material-ui/core/styles'
import Paragraph from '~/components/layout/Paragraph'
import Button from '~/components/layout/Button'
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 Img from '~/components/layout/Img'
import Row from '~/components/layout/Row'
import Block from '~/components/layout/Block'
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 { shortVersionOf } from '~/logic/wallets/ethAddresses'
import { openAddressInEtherScan } from '~/logic/wallets/getWeb3'
import CircleDot from '~/components/Header/component/CircleDot'
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')
type Props = {
@ -49,10 +49,11 @@ const styles = () => ({
borderRadius: '3px',
backgroundColor: background,
margin: '0 auto',
padding: sm,
padding: '9px',
lineHeight: 1,
},
details: {
padding: `0 ${lg}`,
padding: `0 ${md}`,
height: '20px',
alignItems: 'center',
},
@ -60,6 +61,11 @@ const styles = () => ({
flexGrow: 1,
textAlign: 'center',
letterSpacing: '-0.5px',
fontSize: '12px',
},
labels: {
fontSize: '12px',
letterSpacing: '0.5px',
},
open: {
paddingLeft: sm,
@ -77,6 +83,17 @@ const styles = () => ({
logo: {
margin: `0px ${xs}`,
},
dot: {
marginRight: xs,
height: '15px',
width: '15px',
},
warning: {
color: warning,
},
connected: {
color: connectedBg,
},
})
const UserDetails = ({
@ -85,14 +102,16 @@ const UserDetails = ({
const status = connected ? 'Connected' : 'Connection error'
const address = userAddress ? shortVersionOf(userAddress, 6) : 'Address not available'
const identiconAddress = userAddress || 'random'
const connectionLogo = connected ? connectedLogo : connectedWarning
const color = connected ? 'primary' : 'warning'
return (
<React.Fragment>
<Block className={classes.container}>
<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>
<Block align="center" className={classes.user}>
<Paragraph className={classes.address} size="sm" noMargin>{address}</Paragraph>
@ -107,33 +126,29 @@ const UserDetails = ({
</Block>
<Hairline margin="xs" />
<Row className={classes.details}>
<Paragraph size="sm" noMargin align="right">Status </Paragraph>
<Paragraph noMargin align="right" className={classes.labels}>Status </Paragraph>
<Spacer />
<Img className={classes.logo} src={connectionLogo} height={16} alt="Conection Status" />
<Paragraph size="sm" noMargin align="right" color={color}>
<Bold>
{status}
</Bold>
<Dot className={classNames(classes.dot, connected ? classes.connected : classes.warning)} />
<Paragraph noMargin align="right" color={color} weight="bolder" className={classes.labels}>
{status}
</Paragraph>
</Row>
<Hairline margin="xs" />
<Row className={classes.details}>
<Paragraph size="sm" noMargin align="right">Client </Paragraph>
<Paragraph noMargin align="right" className={classes.labels}>Client </Paragraph>
<Spacer />
<Img className={classes.logo} src={metamask} height={16} alt="Metamask client" />
<Paragraph size="sm" noMargin align="right">
<Bold>
{upperFirst(provider)}
</Bold>
<Img className={classes.logo} src={metamask} height={14} alt="Metamask client" />
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
{upperFirst(provider)}
</Paragraph>
</Row>
<Hairline margin="xs" />
<Row className={classes.details}>
<Paragraph size="sm" noMargin align="right">Network </Paragraph>
<Paragraph noMargin align="right" className={classes.labels}>Network </Paragraph>
<Spacer />
<Img className={classes.logo} src={dot} height={16} alt="Network" />
<Paragraph size="sm" noMargin align="right">
<Bold>{upperFirst(network)}</Bold>
<Img className={classes.logo} src={dot} height={14} alt="Network" />
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
{upperFirst(network)}
</Paragraph>
</Row>
<Hairline margin="xs" />
@ -145,7 +160,7 @@ const UserDetails = ({
color="primary"
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>
</Row>
</React.Fragment>

View File

@ -3,13 +3,11 @@ import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import Paragraph from '~/components/layout/Paragraph'
import Col from '~/components/layout/Col'
import Img from '~/components/layout/Img'
import { sm } from '~/theme/variables'
import Dot from '@material-ui/icons/FiberManualRecord'
import { connected as connectedBg, sm } from '~/theme/variables'
import Identicon from '~/components/Identicon'
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
const connectedLogo = require('../../assets/connected.svg')
const connectedWarning = require('../../assets/connected-error.svg')
import CircleDot from '~/components/Header/component/CircleDot'
type Props = {
provider: string,
@ -24,15 +22,21 @@ const styles = () => ({
fontFamily: 'Montserrat, sans-serif',
},
logo: {
top: '10px',
height: '15px',
width: '15px',
top: '12px',
position: 'relative',
right: '13px',
right: '10px',
backgroundColor: '#ffffff',
borderRadius: '15px',
color: connectedBg,
},
account: {
paddingRight: sm,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
justifyContent: 'left',
alignItems: 'start',
flexGrow: 1,
},
address: {
@ -46,15 +50,21 @@ const ProviderInfo = ({
const providerText = `${provider} [${network}]`
const cutAddress = connected ? shortVersionOf(userAddress, 6) : 'Connection Error'
const color = connected ? 'primary' : 'warning'
const logo = connected ? connectedLogo : connectedWarning
const identiconAddress = userAddress || 'random'
return (
<React.Fragment>
<Identicon address={identiconAddress} diameter={30} />
<Img className={classes.logo} src={logo} height={20} alt="Connection status" />
{ connected &&
<React.Fragment>
<Identicon address={identiconAddress} diameter={30} />
<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}>
<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>
</Col>
</React.Fragment>

View File

@ -3,11 +3,9 @@ import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import Paragraph from '~/components/layout/Paragraph'
import Col from '~/components/layout/Col'
import Img from '~/components/layout/Img'
import { type Open } from '~/components/hoc/OpenHoc'
import { md } from '~/theme/variables'
const connectWallet = require('../../assets/connect-wallet.svg')
import { sm } from '~/theme/variables'
import CircleDot from '~/components/Header/component/CircleDot'
type Props = Open & {
classes: Object,
@ -19,10 +17,11 @@ const styles = () => ({
fontFamily: 'Montserrat, sans-serif',
},
account: {
padding: `0 ${md}`,
paddingRight: sm,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'start',
flexGrow: 1,
},
connect: {
@ -32,7 +31,7 @@ const styles = () => ({
const ProviderDesconnected = ({ classes }: Props) => (
<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}>
<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>

View File

@ -4,10 +4,10 @@ import { connect } from 'react-redux'
import { logComponentStack, type Info } from '~/utils/logBoundaries'
import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar/Context'
import { WALLET_ERROR_MSG } from '~/logic/wallets/store/actions'
import ProviderInfo from './component/ProviderInfo'
import ProviderDetails from './component/ProviderInfo/UserDetails'
import ProviderDisconnected from './component/ProviderDisconnected'
import ConnectDetails from './component/ProviderDisconnected/ConnectDetails'
import ProviderAccesible from './component/ProviderInfo/ProviderAccesible'
import UserDetails from './component/ProviderDetails/UserDetails'
import ProviderDisconnected from './component/ProviderInfo/ProviderDisconnected'
import ConnectDetails from './component/ProviderDetails/ConnectDetails'
import Layout from './component/Layout'
import actions, { type Actions } from './actions'
import selector, { type SelectorProps } from './selector'
@ -54,7 +54,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
return <ProviderDisconnected />
}
return <ProviderInfo provider={provider} network={network} userAddress={userAddress} connected={available} />
return <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected={available} />
}
getProviderDetailsBased = () => {
@ -67,7 +67,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
return <ConnectDetails onConnect={this.onConnect} />
}
return (<ProviderDetails
return (<UserDetails
provider={provider}
network={network}
userAddress={userAddress}

View File

@ -29,6 +29,8 @@ const styles = () => ({
'&:focus': {
outline: 'none',
},
display: 'flex',
flexDirection: 'column',
},
})

View File

@ -10,7 +10,7 @@ import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
import WarningIcon from '@material-ui/icons/Warning'
import { type WithStyles } from '~/theme/mui'
import { secondary } from '~/theme/variables'
import { secondary, warning, connected } from '~/theme/variables'
type Variant = 'success' | 'error' | 'warning' | 'info'
@ -39,13 +39,13 @@ const styles = theme => ({
backgroundColor: '#ffffff',
},
successIcon: {
color: '#00c4c4',
color: connected,
},
warning: {
backgroundColor: '#fff3e2',
},
warningIcon: {
color: '#ffc05f',
color: warning,
},
error: {
backgroundColor: '#ffe6ea',

View File

@ -3,7 +3,7 @@ import * as React from 'react'
import Button from '~/components/layout/Button'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import { sm } from '~/theme/variables'
import { sm, boldFont } from '~/theme/variables'
const controlStyle = {
backgroundColor: 'white',
@ -12,6 +12,11 @@ const controlStyle = {
const firstButtonStyle = {
marginRight: sm,
fontWeight: boldFont,
}
const secondButtonStyle = {
fontWeight: boldFont,
}
type Props = {
@ -41,6 +46,7 @@ const Controls = ({
{back}
</Button>
<Button
style={secondButtonStyle}
size="small"
variant="raised"
color="primary"

View File

@ -9,6 +9,7 @@ const styles = () => ({
root: {
margin: '10px',
maxWidth: '870px',
boxShadow: '0 0 10px 0 rgba(33,48,77,0.10)',
},
container: {
letterSpacing: '-0.5px',

View File

@ -1,11 +1,14 @@
// @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'
@ -48,8 +51,10 @@ const styles = {
boxShadow: '0 2px 4px 0 rgba(74, 85, 121, 0.5)',
marginBottom: xl,
},
empty: {
loader: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
},
}
@ -78,6 +83,10 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
}))
}
getEmptyStyle = (emptyRows: number) => ({
height: FIXED_HEIGHT * emptyRows,
})
handleChangePage = (e: SyntheticInputEvent<HTMLInputElement>, page: number) => {
this.setState({ page })
}
@ -114,28 +123,33 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
.slice(page * rowsPerPage, ((page * rowsPerPage) + rowsPerPage))
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - (page * rowsPerPage))
const emptyStyle = {
height: FIXED_HEIGHT * emptyRows,
}
const isEmpty = size === 0
return (
<React.Fragment>
<Table aria-labelledby={label} className={classes.root}>
<TableHead
columns={columns}
order={order}
orderBy={orderBy}
onSort={this.onSort}
/>
<TableBody>
{ children(sortedData) }
{ emptyRows > 0 &&
<TableRow style={emptyStyle}>
<TableCell colSpan={4} />
</TableRow>
}
</TableBody>
</Table>
{ !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}

View File

@ -4,7 +4,7 @@ import { border } from '~/theme/variables'
const style = {
height: '100%',
border: `solid 1px ${border}`,
borderRight: `solid 1px ${border}`,
}
const Divider = () => (

View File

@ -6,7 +6,7 @@
.h1 {
line-height: 36px;
font-weight: 500;
font-weight: $bolderFont;
letter-spacing: -1px;
font-size: $(fontSizeHeadingLg)px;
}
@ -29,7 +29,6 @@
.h4 {
line-height: 21px;
letter-spacing: -0.5px;
font-family: 'Roboto Mono', monospace;
font-size: $(fontSizeHeadingXs)px;
}

View File

@ -15,6 +15,9 @@ body {
font-size: $mediumFontSize;
margin: 0;
background-color: $background;
text-rendering: geometricPrecision;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body>div:first-child {

View File

@ -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

View File

@ -1,5 +1,6 @@
// @flow
import * as React from 'react'
import classNames from 'classnames'
import { getNamesFrom, getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Block from '~/components/layout/Block'
import { withStyles } from '@material-ui/core/styles'
@ -41,6 +42,8 @@ const styles = () => ({
name: {
textOverflow: 'ellipsis',
overflow: 'hidden',
},
userName: {
whiteSpace: 'nowrap',
},
owner: {
@ -115,7 +118,7 @@ const ReviewComponent = ({ values, classes, network }: Props) => {
<Identicon address={addresses[index]} diameter={32} />
</Col>
<Col xs={11}>
<Block className={classes.name}>
<Block className={classNames(classes.name, classes.userName)}>
<Paragraph size="lg" noMargin >{name}</Paragraph>
<Block align="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>{addresses[index]}</Paragraph>

View File

@ -36,7 +36,7 @@ const styles = () => ({
const SafeName = ({ classes }: Props) => (
<React.Fragment>
<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.
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>

View File

@ -7,9 +7,8 @@ import { required, composeValidators, uniqueAddress, mustBeEthereumAddress } fro
import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button'
import Row from '~/components/layout/Row'
import Img from '~/components/layout/Img'
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 CheckCircle from '@material-ui/icons/CheckCircle'
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 { md, lg, sm } from '~/theme/variables'
const trash = require('../../assets/trash.svg')
type Props = {
classes: Object,
otherAccounts: string[],
@ -57,6 +58,14 @@ const styles = () => ({
color: '#03AE60',
height: '20px',
},
remove: {
height: '56px',
marginTop: '12px',
maxWidth: '50px',
'&:hover': {
cursor: 'pointer',
},
},
})
const getAddressValidators = (addresses: string[], position: number) => {
@ -115,7 +124,7 @@ class SafeOwners extends React.Component<Props, State> {
return (
<React.Fragment>
<Block className={classes.title}>
<Paragraph noMargin size="md" color="primary" weight="light">
<Paragraph noMargin size="md" color="primary">
Specify the owners of the Safe.
</Paragraph>
</Block>
@ -159,11 +168,9 @@ class SafeOwners extends React.Component<Props, State> {
text="Owner Address"
/>
</Col>
<Col xs={1} center="xs" middle="xs">
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
{ index > 0 &&
<IconButton aria-label="Delete" onClick={this.onRemoveRow(index)} className={classes.trash}>
<Delete />
</IconButton>
<Img src={trash} height={20} alt="Delete" onClick={this.onRemoveRow(index)} />
}
</Col>
</Row>
@ -172,7 +179,7 @@ class SafeOwners extends React.Component<Props, State> {
</Block>
<Row align="center" grow className={classes.add} margin="xl">
<Button color="secondary" onClick={this.onAddOwner}>
{ADD_OWNER_BUTTON}
<Paragraph weight="bold" size="md" noMargin>{ADD_OWNER_BUTTON}</Paragraph>
</Button>
</Row>
</React.Fragment>

View File

@ -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,
}

View File

@ -1,92 +1,76 @@
// @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 { lg, md, sm, xs, mediumFontSize } from '~/theme/variables'
import { type Token } from '~/routes/tokens/store/model/token'
import actions, { type Actions } from './actions'
import { styles } from './style'
const styles = () => ({
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,
},
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',
},
},
})
type Props = {
type Props = Actions & {
onClose: () => void,
classes: Object,
tokens: List<Token>,
safeAddress: string,
}
class Tokens extends React.Component<Props> {
requestSearch = () => {
// eslint-disable-next-line
console.log("Filtering by name or symbol...")
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 } = this.props
const { onClose, classes, tokens } = this.props
const searchClasses = {
input: classes.searchInput,
root: classes.searchRoot,
@ -94,34 +78,58 @@ class Tokens extends React.Component<Props> {
searchContainer: classes.searchContainer,
}
const filteredTokens = filterBy(this.state.filter, tokens)
return (
<React.Fragment>
<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}
onRequestSearch={this.requestSearch}
searchIcon={<div />}
/>
<Spacer />
<Divider />
<Spacer />
<Button variant="contained" size="small" color="secondary" className={classes.add}>
+ ADD CUSTOM TOKEN
</Button>
</Row>
<Hairline />
<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>
)
}
}
export default withStyles(styles)(Tokens)
const TokenComponent = withStyles(styles)(Tokens)
export default connect(undefined, actions)(TokenComponent)

View File

@ -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',
},
},
})

View File

@ -1,5 +1,6 @@
// @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'
@ -14,39 +15,17 @@ type BalanceData = {
export type BalanceRow = SortRow<BalanceData>
export const getBalanceData = (): Array<BalanceRow> => [
{
[BALANCE_TABLE_ASSET_ID]: 'CVL Journalism',
[BALANCE_TABLE_BALANCE_ID]: '234 CVL',
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: 234,
},
{
[BALANCE_TABLE_ASSET_ID]: 'ABC Periodico',
[BALANCE_TABLE_BALANCE_ID]: '1.394 ABC',
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: 1.394,
},
{
[BALANCE_TABLE_ASSET_ID]: 'Ethereum',
[BALANCE_TABLE_BALANCE_ID]: '9.394 ETH',
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: 9.394,
[FIXED]: true,
},
{
[BALANCE_TABLE_ASSET_ID]: 'Gnosis',
[BALANCE_TABLE_BALANCE_ID]: '0.599 GNO',
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: 0.559,
},
{
[BALANCE_TABLE_ASSET_ID]: 'OmiseGO',
[BALANCE_TABLE_BALANCE_ID]: '39.922 OMG',
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: 39.922,
},
{
[BALANCE_TABLE_ASSET_ID]: 'Moe Feo',
[BALANCE_TABLE_BALANCE_ID]: '0 MOE',
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: 0,
},
]
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 = {

View File

@ -1,6 +1,8 @@
// @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'
@ -14,11 +16,11 @@ 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 { sm, xs } from '~/theme/variables'
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,
@ -27,58 +29,12 @@ type State = {
showSend: boolean,
}
const styles = theme => ({
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',
},
},
})
type Props = {
classes: Object,
granted: boolean,
tokens: List<Token>,
activeTokens: List<Token>,
safeAddress: string,
}
type Action = 'Token' | 'Send' | 'Receive'
@ -109,7 +65,9 @@ class Balances extends React.Component<Props, State> {
const {
hideZero, showToken, showReceive, showSend,
} = this.state
const { classes, granted } = this.props
const {
classes, granted, tokens, safeAddress, activeTokens,
} = this.props
const columns = generateColumns()
const autoColumns = columns.filter(c => !c.custom)
@ -117,7 +75,7 @@ class Balances extends React.Component<Props, State> {
root: classes.root,
}
const filteredData = filterByZero(getBalanceData(), hideZero)
const filteredData = filterByZero(getBalanceData(activeTokens), hideZero)
return (
<React.Fragment>
@ -142,7 +100,7 @@ class Balances extends React.Component<Props, State> {
handleClose={this.onHide('Token')}
open={showToken}
>
<Tokens onClose={this.onHide('Token')} />
<Tokens tokens={tokens} onClose={this.onHide('Token')} safeAddress={safeAddress} />
</Modal>
</Col>
</Row>

View File

@ -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',
},
},
})

View File

@ -76,12 +76,12 @@ class Layout extends React.Component<Props, State> {
render() {
const {
safe, provider, network, classes, granted,
safe, provider, network, classes, granted, tokens, activeTokens,
} = this.props
const { value } = this.state
if (!safe) {
return <NoSafe provider={provider} text="Not found safe" />
return <NoSafe provider={provider} text="Safe not found" />
}
// <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
const address = safe.get('address')
@ -122,7 +122,13 @@ class Layout extends React.Component<Props, State> {
</Tabs>
</Row>
<Hairline color="#c8ced4" />
{value === 0 && <Balances granted={granted} />}
{value === 0 &&
<Balances
tokens={tokens}
activeTokens={activeTokens}
granted={granted}
safeAddress={address}
/>}
</React.Fragment>
)
}

View File

@ -15,6 +15,7 @@ const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
class SafeView extends React.PureComponent<Props> {
componentDidMount() {
this.props.fetchSafe(this.props.safeUrl)
this.props.fetchTokens(this.props.safeUrl)
this.intervalId = setInterval(() => {
const { safeUrl, fetchTokens, fetchSafe } = this.props
fetchTokens(safeUrl)
@ -41,13 +42,14 @@ class SafeView extends React.PureComponent<Props> {
render() {
const {
safe, provider, activeTokens, granted, userAddress, network,
safe, provider, activeTokens, granted, userAddress, network, tokens,
} = this.props
return (
<Page>
<Layout
activeTokens={activeTokens}
tokens={tokens}
provider={provider}
safe={safe}
userAddress={userAddress}

View File

@ -7,13 +7,14 @@ import { type Safe } from '~/routes/safe/store/model/safe'
import { type Owner } from '~/routes/safe/store/model/owner'
import { type GlobalState } from '~/store'
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 { safeParamAddressSelector } from '../store/selectors'
export type SelectorProps = {
safe: SafeSelectorProps,
provider: string,
tokens: List<Token>,
activeTokens: List<Token>,
userAddress: string,
network: string,
@ -44,6 +45,7 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
export default createStructuredSelector({
safe: safeSelector,
provider: providerNameSelector,
tokens: orderedTokenListSelector,
activeTokens: activeTokensSelector,
granted: grantedSelector,
userAddress: userAccountSelector,

View File

@ -30,6 +30,11 @@ export const activeTokensSelector = createSelector(
(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(
tokenListSelector,
(balances: List<Token>) => {

View File

@ -6,7 +6,7 @@ import Img from '~/components/layout/Img'
import Button from '~/components/layout/Button'
import Link from '~/components/layout/Link'
import { OPEN_ADDRESS } from '~/routes/routes'
import { sm } from '~/theme/variables'
import { marginButtonImg } from '~/theme/variables'
import styles from './Layout.scss'
const safe = require('../assets/safe.svg')
@ -22,7 +22,7 @@ type SafeProps = {
}
const buttonStyle = {
marginLeft: sm,
marginLeft: marginButtonImg,
}
export const CreateSafe = ({ size, provider }: SafeProps) => (
@ -35,7 +35,7 @@ export const CreateSafe = ({ size, provider }: SafeProps) => (
disabled={!provider}
minWidth={240}
>
<Img src={plus} height={16} alt="Safe" />
<Img src={plus} height={14} alt="Safe" />
<div style={buttonStyle}>Create new Safe</div>
</Button>
)
@ -50,7 +50,7 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
disabled={!provider}
minWidth={240}
>
<Img src={safe} height={16} alt="Safe" />
<Img src={safe} height={14} alt="Safe" />
<div style={buttonStyle}>Load existing Safe</div>
</Button>
)
@ -58,10 +58,8 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
const Welcome = ({ provider }: Props) => (
<Block className={styles.safe}>
<Heading tag="h1" align="center">
Welcome to the Gnosis
</Heading>
<Heading tag="h1" align="center" margin="lg">
Welcome to the Gnosis <br />
Safe Team Edition
</Heading>
<Heading tag="h4" align="center" margin="xl">

View File

@ -1,7 +1,6 @@
// @flow
import red from '@material-ui/core/colors/red'
import { createMuiTheme } from '@material-ui/core/styles'
import { largeFontSize, mediumFontSize, smallFontSize, disabled, primary, secondary, md, lg, background, bolderFont, boldFont } from './variables'
import { largeFontSize, mediumFontSize, smallFontSize, disabled, primary, secondary, md, lg, bolderFont, boldFont, buttonLargeFontSize } from './variables'
export type WithStyles = {
classes: Object,
@ -14,7 +13,9 @@ const palette = {
secondary: {
main: secondary,
},
error: red,
error: {
main: '#FB4F62',
},
contrastThreshold: 3,
tonalOffset: 0.2,
}
@ -29,7 +30,7 @@ export default createMuiTheme({
MuiButton: {
root: {
fontFamily: 'Roboto Mono, monospace',
letterSpacing: '1px',
letterSpacing: '0.9px',
'&:disabled': {
color: disabled,
},
@ -38,28 +39,49 @@ export default createMuiTheme({
disabled: {
cursor: 'pointer',
},
contained: {
boxShadow: 'none',
},
containedPrimary: {
backgroundColor: secondary,
},
sizeLarge: {
padding: `${md} ${lg}`,
minHeight: '52px',
fontSize: mediumFontSize,
fontSize: buttonLargeFontSize,
fontWeight: boldFont,
},
sizeSmall: {
minWidth: '130px',
fontSize: smallFontSize,
},
textSecondary: {
'&:hover': {
borderRadius: '3px',
},
},
},
MuiStepper: {
root: {
padding: '24px 0 0 15px',
},
},
MuiStepIcon: {
root: {
fontSize: '22px',
color: '#A2A8BA !important',
},
completed: {
color: `${secondary} !important`,
},
active: {
color: `${secondary} !important`,
fontWeight: boldFont,
},
},
MuiStepContent: {
root: {
borderLeft: '1px solid #A2A8BA',
},
},
MuiTypography: {
@ -72,14 +94,14 @@ export default createMuiTheme({
MuiFormHelperText: {
root: {
fontFamily: 'Roboto Mono, monospace',
fontSize: smallFontSize,
fontSize: '12px',
padding: `0 0 0 ${md}`,
position: 'relative',
top: '20px',
color: secondary,
order: 0,
marginTop: '0px',
backgroundColor: background,
backgroundColor: 'EAE9EF',
},
},
MuiInput: {
@ -90,16 +112,20 @@ export default createMuiTheme({
lineHeight: '56px',
order: 1,
padding: `0 ${md}`,
backgroundColor: background,
backgroundColor: '#EAE9EF',
'&:$disabled': {
color: '#0000ff',
},
},
input: {
padding: 0,
color: 'initial',
letterSpacing: '0.5px',
color: primary,
textOverflow: 'ellipsis',
display: 'flex',
'&::-webkit-input-placeholder': {
color: disabled,
},
},
underline: {
'&:before': {
@ -116,6 +142,10 @@ export default createMuiTheme({
MuiStepLabel: {
label: {
textAlign: 'left',
color: '#A2A8BA',
'&$active': {
color: primary,
},
},
},
MuiSnackbarContent: {
@ -181,6 +211,19 @@ export default createMuiTheme({
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,
})

View File

@ -1,12 +1,13 @@
// @flow
const border = '#eaebef'
const border = '#e4e8f1'
const background = '#f4f4f9'
const primary = '#4a5579'
const secondary = '#467ee5' // '#13222b'
const tertiary = '#f6f9fc'
const fontColor = '#4a5579'
const fancyColor = '#fd7890'
const warningColor = '#c97c05'
const warningColor = '#ffc05f'
const connectedColor = '#00c4c4'
const disabled = '#65707e'
const xs = '4px'
const sm = '8px'
@ -14,6 +15,7 @@ const md = '16px'
const lg = '24px'
const xl = '32px'
const xxl = '40px'
const marginButtonImg = '12px'
module.exports = Object.assign({}, {
primary,
@ -24,6 +26,7 @@ module.exports = Object.assign({}, {
fontColor,
fancy: fancyColor,
warning: warningColor,
connected: connectedColor,
xs,
sm,
md,
@ -31,10 +34,12 @@ module.exports = Object.assign({}, {
xl,
xxl,
border,
marginButtonImg,
fontSizeHeadingXs: 13,
fontSizeHeadingSm: 18,
fontSizeHeadingMd: 21,
fontSizeHeadingLg: 32,
buttonLargeFontSize: '12px',
lightFont: 300,
regularFont: 400,
bolderFont: 500,