Merge pull request #233 from gnosis/development

Development to master
This commit is contained in:
Mikhail Mikheev 2019-10-24 13:25:20 +04:00 committed by GitHub
commit 625e414fcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1412 additions and 850 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ build_webpack/
build_storybook/ build_storybook/
.DS_Store .DS_Store
build/ build/
yarn-error.log yarn-error.log
.env.*

View File

@ -33,14 +33,14 @@
"dependencies": { "dependencies": {
"@gnosis.pm/safe-contracts": "^1.0.0", "@gnosis.pm/safe-contracts": "^1.0.0",
"@gnosis.pm/util-contracts": "2.0.4", "@gnosis.pm/util-contracts": "2.0.4",
"@material-ui/core": "4.5.0", "@material-ui/core": "4.5.1",
"@material-ui/icons": "4.4.3", "@material-ui/icons": "4.5.1",
"@testing-library/jest-dom": "4.1.0", "@testing-library/jest-dom": "4.1.2",
"@welldone-software/why-did-you-render": "3.3.5", "@welldone-software/why-did-you-render": "3.3.8",
"axios": "0.19.0", "axios": "0.19.0",
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"connected-react-router": "6.5.2", "connected-react-router": "6.5.2",
"date-fns": "2.4.1", "date-fns": "2.5.0",
"ethereum-ens": "0.7.8", "ethereum-ens": "0.7.8",
"final-form": "4.18.5", "final-form": "4.18.5",
"history": "4.10.1", "history": "4.10.1",
@ -54,7 +54,7 @@
"react-dom": "16.10.2", "react-dom": "16.10.2",
"react-final-form": "6.3.0", "react-final-form": "6.3.0",
"react-final-form-listeners": "^1.0.2", "react-final-form-listeners": "^1.0.2",
"react-hot-loader": "4.12.14", "react-hot-loader": "4.12.15",
"react-infinite-scroll-component": "4.5.3", "react-infinite-scroll-component": "4.5.3",
"react-qr-reader": "^2.2.1", "react-qr-reader": "^2.2.1",
"react-redux": "7.1.1", "react-redux": "7.1.1",
@ -68,8 +68,8 @@
"web3": "1.2.1" "web3": "1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.6.2", "@babel/cli": "7.6.4",
"@babel/core": "7.6.2", "@babel/core": "7.6.4",
"@babel/plugin-proposal-class-properties": "7.5.5", "@babel/plugin-proposal-class-properties": "7.5.5",
"@babel/plugin-proposal-decorators": "7.6.0", "@babel/plugin-proposal-decorators": "7.6.0",
"@babel/plugin-proposal-do-expressions": "7.6.0", "@babel/plugin-proposal-do-expressions": "7.6.0",
@ -89,16 +89,16 @@
"@babel/plugin-transform-member-expression-literals": "^7.2.0", "@babel/plugin-transform-member-expression-literals": "^7.2.0",
"@babel/plugin-transform-property-literals": "^7.2.0", "@babel/plugin-transform-property-literals": "^7.2.0",
"@babel/polyfill": "7.6.0", "@babel/polyfill": "7.6.0",
"@babel/preset-env": "7.6.2", "@babel/preset-env": "7.6.3",
"@babel/preset-flow": "^7.0.0", "@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "7.6.3",
"@sambego/storybook-state": "^1.3.6", "@sambego/storybook-state": "^1.3.6",
"@storybook/addon-actions": "5.2.1", "@storybook/addon-actions": "5.2.4",
"@storybook/addon-knobs": "5.2.1", "@storybook/addon-knobs": "5.2.4",
"@storybook/addon-links": "5.2.1", "@storybook/addon-links": "5.2.4",
"@storybook/react": "5.2.1", "@storybook/react": "5.2.4",
"@testing-library/react": "9.3.0", "@testing-library/react": "9.3.0",
"autoprefixer": "9.6.1", "autoprefixer": "9.6.5",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.0.3", "babel-eslint": "10.0.3",
"babel-jest": "24.9.0", "babel-jest": "24.9.0",
@ -113,9 +113,9 @@
"eslint-config-airbnb": "18.0.1", "eslint-config-airbnb": "18.0.1",
"eslint-plugin-flowtype": "4.3.0", "eslint-plugin-flowtype": "4.3.0",
"eslint-plugin-import": "2.18.2", "eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "22.17.0", "eslint-plugin-jest": "22.19.0",
"eslint-plugin-jsx-a11y": "6.2.3", "eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.15.0", "eslint-plugin-react": "7.16.0",
"ethereumjs-abi": "0.6.8", "ethereumjs-abi": "0.6.8",
"extract-text-webpack-plugin": "^4.0.0-beta.0", "extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "4.2.0", "file-loader": "4.2.0",
@ -136,13 +136,13 @@
"storybook-host": "5.1.0", "storybook-host": "5.1.0",
"storybook-router": "^0.3.4", "storybook-router": "^0.3.4",
"style-loader": "1.0.0", "style-loader": "1.0.0",
"truffle": "5.0.39", "truffle": "5.0.40",
"truffle-contract": "4.0.31", "truffle-contract": "4.0.31",
"truffle-solidity-loader": "0.1.32", "truffle-solidity-loader": "0.1.32",
"uglifyjs-webpack-plugin": "2.2.0", "uglifyjs-webpack-plugin": "2.2.0",
"url-loader": "^2.1.0", "url-loader": "2.2.0",
"webpack": "4.41.0", "webpack": "4.41.2",
"webpack-bundle-analyzer": "3.5.2", "webpack-bundle-analyzer": "3.6.0",
"webpack-cli": "3.3.9", "webpack-cli": "3.3.9",
"webpack-dev-server": "3.8.2", "webpack-dev-server": "3.8.2",
"webpack-manifest-plugin": "2.2.0" "webpack-manifest-plugin": "2.2.0"

View File

@ -1,14 +1,20 @@
// @flow // @flow
import React, { Component } from 'react' import { Component } from 'react'
import { List } from 'immutable'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { withSnackbar } from 'notistack' import { withSnackbar } from 'notistack'
import actions from './actions' import { type Notification } from '~/logic/notifications/store/models/notification'
import actions, { type Actions } from './actions'
import selector from './selector' import selector from './selector'
class Notifier extends Component { type Props = Actions & {
notifications: List<Notification>,
}
class Notifier extends Component<Props> {
displayed = [] displayed = []
shouldComponentUpdate({ notifications: newSnacks = [] }) { shouldComponentUpdate({ notifications: newSnacks = List() }) {
const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props
if (!newSnacks.size) { if (!newSnacks.size) {
@ -27,7 +33,7 @@ class Notifier extends Component {
if (notExists) { if (notExists) {
continue continue
} }
notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).length notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).size
} }
return notExists return notExists
} }

View File

@ -50,8 +50,8 @@ export const minValue = (min: number) => (value: string) => {
return `Should be at least ${min}` return `Should be at least ${min}`
} }
export const maxValue = (max: number) => (value: string) => { export const maxValue = (max: number | string) => (value: string) => {
if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) <= Number(max)) { if (Number.isNaN(Number(value)) || parseFloat(value, 10) <= parseFloat(max, 10)) {
return undefined return undefined
} }

View File

@ -1,6 +1,6 @@
// @flow // @flow
import * as React from 'react'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import React from 'react'
import { capitalize } from '~/utils/css' import { capitalize } from '~/utils/css'
import styles from './index.scss' import styles from './index.scss'

View File

@ -2,6 +2,7 @@
import contract from 'truffle-contract' import contract from 'truffle-contract'
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json' import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json'
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json' import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
import { ensureOnce } from '~/utils/singleton' import { ensureOnce } from '~/utils/singleton'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions' import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
@ -99,3 +100,27 @@ export const getGnosisSafeInstanceAt = async (safeAddress: string) => {
return gnosisSafe return gnosisSafe
} }
const cleanByteCodeMetadata = (bytecode: string): string => {
const metaData = 'a165'
return bytecode.substring(0, bytecode.lastIndexOf(metaData))
}
export const validateProxy = async (safeAddress: string): boolean => {
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
const web3 = getWeb3()
const code = await web3.eth.getCode(safeAddress)
const codeWithoutMetadata = cleanByteCodeMetadata(code)
const supportedProxies = [SafeProxy]
for (let i = 0; i < supportedProxies.length; i += 1) {
const proxy = supportedProxies[i]
const proxyCode = proxy.deployedBytecode
const proxyCodeWithoutMetadata = cleanByteCodeMetadata(proxyCode)
if (codeWithoutMetadata === proxyCodeWithoutMetadata) {
return true
}
}
// Old PayingProxyCode
const oldProxyCode = '0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
return codeWithoutMetadata === oldProxyCode
}

View File

@ -5,7 +5,7 @@ import { Close as IconClose } from '@material-ui/icons'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { type Notification, NOTIFICATIONS } from './notificationTypes' import { type Notification, NOTIFICATIONS } from './notificationTypes'
type NotificationsQueue = { export type NotificationsQueue = {
beforeExecution: Notification, beforeExecution: Notification,
pendingExecution: { pendingExecution: {
noMoreConfirmationsNeeded: Notification, noMoreConfirmationsNeeded: Notification,
@ -104,7 +104,7 @@ const defaultNotificationsQueue: NotificationsQueue = {
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG, afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
} }
export const getNofiticationsFromTxType = (txType: string) => { export const getNotificationsFromTxType = (txType: string) => {
let notificationsQueue: NotificationsQueue let notificationsQueue: NotificationsQueue
switch (txType) { switch (txType) {

View File

@ -1,6 +1,6 @@
// @flow // @flow
import { createAction } from 'redux-actions' import { createAction } from 'redux-actions'
import type { Dispatch as ReduxDispatch, GetState } from 'redux' import type { Dispatch as ReduxDispatch } from 'redux'
import { type GlobalState } from '~/store' import { type GlobalState } from '~/store'
import { type NotificationProps } from '~/logic/notifications/store/models/notification' import { type NotificationProps } from '~/logic/notifications/store/models/notification'
@ -10,7 +10,6 @@ const addSnackbar = createAction<string, *>(ENQUEUE_SNACKBAR)
const enqueueSnackbar = (notification: NotificationProps) => ( const enqueueSnackbar = (notification: NotificationProps) => (
dispatch: ReduxDispatch<GlobalState>, dispatch: ReduxDispatch<GlobalState>,
getState: GetState<GlobalState>,
) => { ) => {
const newNotification = { const newNotification = {
...notification, ...notification,

View File

@ -29,7 +29,5 @@ export default handleActions<NotificationReducerState, *>(
return state.delete(key) return state.delete(key)
}, },
}, },
Map({ Map(),
notifications: Map(),
}),
) )

View File

@ -5,7 +5,7 @@ import { type GlobalState } from '~/store'
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications' import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
import { type Notification } from '~/logic/notifications/store/models/notification' import { type Notification } from '~/logic/notifications/store/models/notification'
export const notificationsMapSelector = ( const notificationsMapSelector = (
state: GlobalState, state: GlobalState,
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID] ): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]

View File

@ -0,0 +1,57 @@
// @flow
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { getWeb3, getAccountFrom } from '~/logic/wallets/getWeb3'
import { generateSignaturesFromTxConfirmations } from '~/routes/safe/store/actions/processTransaction'
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
import { CALL } from '.'
export const estimateTxGasCosts = async (
safeAddress: string,
to: string,
data: string,
tx?: Transaction,
preApprovingOwner?: string,
): Promise<number> => {
try {
const web3 = getWeb3()
const from = await getAccountFrom(web3)
const safeInstance = new web3.eth.Contract(GnosisSafeSol.abi, safeAddress)
const nonce = await safeInstance.methods.nonce().call()
const threshold = await safeInstance.methods.getThreshold().call()
const isExecution = (tx && tx.confirmations.size === threshold) || !!preApprovingOwner || threshold === '1'
let txData
if (isExecution) {
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
const signatures = tx && tx.confirmations
? generateSignaturesFromTxConfirmations(tx.confirmations, preApprovingOwner)
: `0x000000000000000000000000${from.replace(
'0x',
'',
)}000000000000000000000000000000000000000000000000000000000000000001`
txData = await safeInstance.methods
.execTransaction(to, tx ? tx.value : 0, data, CALL, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, signatures)
.encodeABI()
} else {
const txHash = await safeInstance.methods
.getTransactionHash(to, tx ? tx.value : 0, data, CALL, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, nonce)
.call({
from,
})
txData = await safeInstance.methods.approveHash(txHash).encodeABI()
}
const gas = await calculateGasOf(txData, from, safeAddress)
const gasPrice = await calculateGasPrice()
return gas * parseInt(gasPrice, 10)
} catch (err) {
console.error('Error while estimating transaction execution gas costs:')
console.error(err)
return 10000
}
}

View File

@ -0,0 +1,47 @@
// @flow
// https://github.com/ethers-io/ethers.js/issues/527
export const ALTERNATIVE_TOKEN_ABI = [
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '',
type: 'bytes32',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '',
type: 'bytes32',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'decimals',
outputs: [
{
name: '',
type: 'uint8',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
]

View File

@ -17,7 +17,11 @@ const lt1000tFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 3, n
export const formatAmount = (number: string | number) => { export const formatAmount = (number: string | number) => {
let numberFloat = parseFloat(number) let numberFloat = parseFloat(number)
if (numberFloat < 1000) { if (numberFloat === 0) {
numberFloat = '0.000'
} else if (numberFloat < 0.001) {
numberFloat = '< 0.001'
} else if (numberFloat < 1000) {
numberFloat = lt1kFormatter.format(numberFloat) numberFloat = lt1kFormatter.format(numberFloat)
} else if (numberFloat < 10000) { } else if (numberFloat < 10000) {
numberFloat = lt10kFormatter.format(numberFloat) numberFloat = lt10kFormatter.format(numberFloat)

View File

@ -56,6 +56,6 @@ export const calculateGasOf = async (data: Object, from: string, to: string) =>
return gas * 2 return gas * 2
} catch (err) { } catch (err) {
return Promise.reject(new Error(err)) return Promise.reject(err)
} }
} }

View File

@ -64,7 +64,7 @@ const getProviderName: Function = (web3Provider): string => {
return name return name
} }
const getAccountFrom: Function = async (web3Provider): Promise<string | null> => { export const getAccountFrom: Function = async (web3Provider): Promise<string | null> => {
const accounts = await web3Provider.eth.getAccounts() const accounts = await web3Provider.eth.getAccounts()
if (process.env.NODE_ENV === 'test' && window.testAccountIndex) { if (process.env.NODE_ENV === 'test' && window.testAccountIndex) {

View File

@ -64,7 +64,7 @@ const Routes = ({ defaultSafe, location }: RoutesProps) => {
/> />
<Route exact path={WELCOME_ADDRESS} component={Welcome} /> <Route exact path={WELCOME_ADDRESS} component={Welcome} />
<Route exact path={OPEN_ADDRESS} component={Open} /> <Route exact path={OPEN_ADDRESS} component={Open} />
<Route exact path={SAFE_ADDRESS} component={Safe} /> <Route path={SAFE_ADDRESS} component={Safe} />
<Route exact path={OPENING_ADDRESS} component={Opening} /> <Route exact path={OPENING_ADDRESS} component={Opening} />
<Route exact path={LOAD_ADDRESS} component={Load} /> <Route exact path={LOAD_ADDRESS} component={Load} />
<Redirect to="/" /> <Redirect to="/" />

View File

@ -1,7 +1,6 @@
// @flow // @flow
import * as React from 'react' import * as React from 'react'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
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 Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
@ -15,7 +14,7 @@ import Paragraph from '~/components/layout/Paragraph'
import OpenPaper from '~/components/Stepper/OpenPaper' import OpenPaper from '~/components/Stepper/OpenPaper'
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields' import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { getSafeMasterContract } from '~/logic/contracts/safeContracts' import { getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
import { secondary } from '~/theme/variables' import { secondary } from '~/theme/variables'
type Props = { type Props = {
@ -56,15 +55,8 @@ export const safeFieldsValidation = async (values: Object) => {
return errors return errors
} }
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification const isValidProxy = await validateProxy(safeAddress)
const metaData = 'a165' if (!isValidProxy) {
const code = await web3.eth.getCode(safeAddress)
const codeWithoutMetadata = code.substring(0, code.lastIndexOf(metaData))
const proxyCode = SafeProxy.deployedBytecode
const proxyCodeWithoutMetadata = proxyCode.substring(0, proxyCode.lastIndexOf(metaData))
const safeInstance = codeWithoutMetadata === proxyCodeWithoutMetadata
if (!safeInstance) {
errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR
return errors return errors
} }

View File

@ -12,6 +12,7 @@ import OpenPaper from '~/components/Stepper/OpenPaper'
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 Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { import {
sm, md, lg, border, background, sm, md, lg, border, background,
} from '~/theme/variables' } from '~/theme/variables'
@ -73,7 +74,7 @@ type Props = {
} }
const ReviewComponent = ({ values, classes, userAccount }: Props) => { const ReviewComponent = ({ values, classes, userAccount }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('0.00') const [gasCosts, setGasCosts] = useState<string>('< 0.001')
const names = getNamesFrom(values) const names = getNamesFrom(values)
const addresses = getAccountsFrom(values) const addresses = getAccountsFrom(values)
const numOwners = getNumOwnersFrom(values) const numOwners = getNumOwnersFrom(values)
@ -85,9 +86,9 @@ const ReviewComponent = ({ values, classes, userAccount }: Props) => {
const { fromWei, toBN } = web3.utils const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateGasForDeployingSafe(addresses, numOwners, userAccount) const estimatedGasCosts = await estimateGasForDeployingSafe(addresses, numOwners, userAccount)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether') const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const roundedGasCosts = parseFloat(gasCostsAsEth).toFixed(3) const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) { if (isCurrent) {
setGasCosts(roundedGasCosts) setGasCosts(formattedGasCosts)
} }
} }

View File

@ -28,7 +28,7 @@ import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import trash from '~/assets/icons/trash.svg' import trash from '~/assets/icons/trash.svg'
import QRIcon from '~/assets/icons/qrcode.svg' import QRIcon from '~/assets/icons/qrcode.svg'
import ScanQRModal from './ScanQRModal' import ScanQRModal from '~/components/ScanQRModal'
import { getAddressValidator } from './validators' import { getAddressValidator } from './validators'
import { styles } from './style' import { styles } from './style'

View File

@ -1,23 +1,17 @@
// @flow // @flow
import React from 'react' import React from 'react'
import OpenInNew from '@material-ui/icons/OpenInNew' import { makeStyles } from '@material-ui/core/styles'
import { withStyles } from '@material-ui/core/styles'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Link from '~/components/layout/Link' import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Bold from '~/components/layout/Bold' import Bold from '~/components/layout/Bold'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import { copyToClipboard } from '~/utils/clipboard' import { xs, border } from '~/theme/variables'
import { secondary, xs, border } from '~/theme/variables'
const openIconStyle = { const useStyles = makeStyles({
height: '16px',
color: secondary,
}
const styles = () => ({
balanceContainer: { balanceContainer: {
fontSize: '12px', fontSize: '12px',
lineHeight: 1.08, lineHeight: 1.08,
@ -28,20 +22,22 @@ const styles = () => ({
marginTop: xs, marginTop: xs,
borderRadius: '3px', borderRadius: '3px',
}, },
address: {
marginRight: xs,
},
}) })
type Props = { type Props = {
classes: Object,
safeAddress: string, safeAddress: string,
etherScanLink: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
} }
const SafeInfo = (props: Props) => { const SafeInfo = (props: Props) => {
const { const {
safeAddress, safeName, etherScanLink, ethBalance, classes, safeAddress, safeName, ethBalance,
} = props } = props
const classes = useStyles()
return ( return (
<Row margin="md"> <Row margin="md">
@ -52,21 +48,18 @@ const SafeInfo = (props: Props) => {
<Paragraph weight="bolder" noMargin style={{ lineHeight: 1 }}> <Paragraph weight="bolder" noMargin style={{ lineHeight: 1 }}>
{safeName} {safeName}
</Paragraph> </Paragraph>
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin> <Block justify="left">
{safeAddress} <Paragraph weight="bolder" className={classes.address} noMargin>
<Link to={etherScanLink} target="_blank"> {safeAddress}
<OpenInNew style={openIconStyle} /> </Paragraph>
</Link> <CopyBtn content={safeAddress} />
</Paragraph> <EtherscanBtn type="address" value={safeAddress} />
</Block>
<Block className={classes.balanceContainer}> <Block className={classes.balanceContainer}>
<Paragraph noMargin> <Paragraph noMargin>
Balance: Balance:
{' '} {' '}
<Bold> <Bold>{`${ethBalance} ETH`}</Bold>
{ethBalance}
{' '}
ETH
</Bold>
</Paragraph> </Paragraph>
</Block> </Block>
</Col> </Col>
@ -74,4 +67,4 @@ const SafeInfo = (props: Props) => {
) )
} }
export default withStyles(styles)(SafeInfo) export default SafeInfo

View File

@ -24,7 +24,6 @@ type Props = {
classes: Object, classes: Object,
isOpen: boolean, isOpen: boolean,
safeAddress: string, safeAddress: string,
etherScanLink: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
tokens: List<Token>, tokens: List<Token>,
@ -65,7 +64,6 @@ const Send = ({
isOpen, isOpen,
classes, classes,
safeAddress, safeAddress,
etherScanLink,
safeName, safeName,
ethBalance, ethBalance,
tokens, tokens,
@ -113,7 +111,6 @@ const Send = ({
<SendFunds <SendFunds
onClose={onClose} onClose={onClose}
safeAddress={safeAddress} safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName} safeName={safeName}
ethBalance={ethBalance} ethBalance={ethBalance}
tokens={tokens} tokens={tokens}
@ -128,17 +125,16 @@ const Send = ({
onClose={onClose} onClose={onClose}
setActiveScreen={setActiveScreen} setActiveScreen={setActiveScreen}
safeAddress={safeAddress} safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName} safeName={safeName}
ethBalance={ethBalance} ethBalance={ethBalance}
createTransaction={createTransaction} createTransaction={createTransaction}
tokens={tokens}
/> />
)} )}
{activeScreen === 'sendCustomTx' && ( {activeScreen === 'sendCustomTx' && (
<SendCustomTx <SendCustomTx
onClose={onClose} onClose={onClose}
safeAddress={safeAddress} safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName} safeName={safeName}
ethBalance={ethBalance} ethBalance={ethBalance}
onSubmit={handleCustomTxCreation} onSubmit={handleCustomTxCreation}
@ -151,7 +147,6 @@ const Send = ({
onClose={onClose} onClose={onClose}
setActiveScreen={setActiveScreen} setActiveScreen={setActiveScreen}
safeAddress={safeAddress} safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName} safeName={safeName}
ethBalance={ethBalance} ethBalance={ethBalance}
createTransaction={createTransaction} createTransaction={createTransaction}

View File

@ -1,27 +1,27 @@
// @flow // @flow
import React from 'react' import React, { useState, useEffect } from 'react'
import OpenInNew from '@material-ui/icons/OpenInNew'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import { withSnackbar } from 'notistack' import { withSnackbar } from 'notistack'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import Img from '~/components/layout/Img' import Img from '~/components/layout/Img'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import { copyToClipboard } from '~/utils/clipboard' import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils' import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers' import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import { secondary } from '~/theme/variables'
import { styles } from './style' import { styles } from './style'
type Props = { type Props = {
@ -29,7 +29,6 @@ type Props = {
setActiveScreen: Function, setActiveScreen: Function,
classes: Object, classes: Object,
safeAddress: string, safeAddress: string,
etherScanLink: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
tx: Object, tx: Object,
@ -38,17 +37,11 @@ type Props = {
closeSnackbar: Function, closeSnackbar: Function,
} }
const openIconStyle = {
height: '16px',
color: secondary,
}
const ReviewCustomTx = ({ const ReviewCustomTx = ({
onClose, onClose,
setActiveScreen, setActiveScreen,
classes, classes,
safeAddress, safeAddress,
etherScanLink,
safeName, safeName,
ethBalance, ethBalance,
tx, tx,
@ -56,10 +49,33 @@ const ReviewCustomTx = ({
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
}: Props) => { }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.recipientAddress, tx.data.trim())
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
const submitTx = async () => { const submitTx = async () => {
const web3 = getWeb3() const web3 = getWeb3()
const txRecipient = tx.recipientAddress const txRecipient = tx.recipientAddress
const txData = tx.data const txData = tx.data.trim()
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0 const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
createTransaction( createTransaction(
@ -87,12 +103,7 @@ const ReviewCustomTx = ({
</Row> </Row>
<Hairline /> <Hairline />
<Block className={classes.container}> <Block className={classes.container}>
<SafeInfo <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -111,12 +122,13 @@ const ReviewCustomTx = ({
<Identicon address={tx.recipientAddress} diameter={32} /> <Identicon address={tx.recipientAddress} diameter={32} />
</Col> </Col>
<Col xs={11} layout="column"> <Col xs={11} layout="column">
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin> <Block justify="left">
{tx.recipientAddress} <Paragraph weight="bolder" className={classes.address} noMargin>
<Link to={etherScanLink} target="_blank"> {tx.recipientAddress}
<OpenInNew style={openIconStyle} /> </Paragraph>
</Link> <CopyBtn content={tx.recipientAddress} />
</Paragraph> <EtherscanBtn type="address" value={tx.recipientAddress} />
</Block>
</Col> </Col>
</Row> </Row>
<Row margin="xs"> <Row margin="xs">
@ -125,7 +137,7 @@ const ReviewCustomTx = ({
</Paragraph> </Paragraph>
</Row> </Row>
<Row margin="md" align="center"> <Row margin="md" align="center">
<Img src={getEthAsToken().logoUri} height={28} alt="Ether" onError={setImageToPlaceholder} /> <Img src={getEthAsToken('0').logoUri} height={28} alt="Ether" onError={setImageToPlaceholder} />
<Paragraph size="md" noMargin className={classes.value}> <Paragraph size="md" noMargin className={classes.value}>
{tx.value || 0} {tx.value || 0}
{' ETH'} {' ETH'}
@ -143,6 +155,11 @@ const ReviewCustomTx = ({
</Row> </Row>
</Col> </Col>
</Row> </Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block> </Block>
<Hairline /> <Hairline />
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
@ -165,5 +182,4 @@ const ReviewCustomTx = ({
) )
} }
export default withStyles(styles)(withSnackbar(ReviewCustomTx)) export default withStyles(styles)(withSnackbar(ReviewCustomTx))

View File

@ -1,30 +1,32 @@
// @flow // @flow
import React from 'react' import React, { useEffect, useState } from 'react'
import { List } from 'immutable'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import OpenInNew from '@material-ui/icons/OpenInNew'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import { withSnackbar } from 'notistack' import { withSnackbar } from 'notistack'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import Img from '~/components/layout/Img' import Img from '~/components/layout/Img'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import { copyToClipboard } from '~/utils/clipboard'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils' import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens' import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { type Token } from '~/logic/tokens/store/model/token'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import { secondary } from '~/theme/variables'
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
import { styles } from './style' import { styles } from './style'
type Props = { type Props = {
@ -32,43 +34,70 @@ type Props = {
setActiveScreen: Function, setActiveScreen: Function,
classes: Object, classes: Object,
safeAddress: string, safeAddress: string,
etherScanLink: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
tx: Object, tx: Object,
tokens: List<Token>,
createTransaction: Function, createTransaction: Function,
enqueueSnackbar: Function, enqueueSnackbar: Function,
closeSnackbar: Function, closeSnackbar: Function,
} }
const openIconStyle = {
height: '16px',
color: secondary,
}
const ReviewTx = ({ const ReviewTx = ({
onClose, onClose,
setActiveScreen, setActiveScreen,
classes, classes,
safeAddress, safeAddress,
etherScanLink,
safeName, safeName,
ethBalance, ethBalance,
tx, tx,
tokens,
createTransaction, createTransaction,
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
}: Props) => { }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
const txToken = tokens.find((token) => token.address === tx.token)
const isSendingETH = txToken.address === ETH_ADDRESS
const txRecipient = isSendingETH ? tx.recipientAddress : txToken.address
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
let txData = EMPTY_DATA
if (!isSendingETH) {
const StandardToken = await getHumanFriendlyToken()
const tokenInstance = await StandardToken.at(txToken.address)
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, 0).encodeABI()
}
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, txRecipient, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
const submitTx = async () => { const submitTx = async () => {
const web3 = getWeb3() const web3 = getWeb3()
const isSendingETH = isEther(tx.token.symbol)
const txRecipient = isSendingETH ? tx.recipientAddress : tx.token.address
let txData = EMPTY_DATA let txData = EMPTY_DATA
let txAmount = web3.utils.toWei(tx.amount, 'ether') let txAmount = web3.utils.toWei(tx.amount, 'ether')
if (!isSendingETH) { if (!isSendingETH) {
const HumanFriendlyToken = await getHumanFriendlyToken() const HumanFriendlyToken = await getHumanFriendlyToken()
const tokenInstance = await HumanFriendlyToken.at(tx.token.address) const tokenInstance = await HumanFriendlyToken.at(txToken.address)
const decimals = await tokenInstance.decimals() const decimals = await tokenInstance.decimals()
txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString() txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
@ -104,12 +133,7 @@ const ReviewTx = ({
</Row> </Row>
<Hairline /> <Hairline />
<Block className={classes.container}> <Block className={classes.container}>
<SafeInfo <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -128,12 +152,13 @@ const ReviewTx = ({
<Identicon address={tx.recipientAddress} diameter={32} /> <Identicon address={tx.recipientAddress} diameter={32} />
</Col> </Col>
<Col xs={11} layout="column"> <Col xs={11} layout="column">
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin> <Block justify="left">
{tx.recipientAddress} <Paragraph weight="bolder" className={classes.address} noMargin>
<Link to={etherScanLink} target="_blank"> {tx.recipientAddress}
<OpenInNew style={openIconStyle} /> </Paragraph>
</Link> <CopyBtn content={tx.recipientAddress} />
</Paragraph> <EtherscanBtn type="address" value={tx.recipientAddress} />
</Block>
</Col> </Col>
</Row> </Row>
<Row margin="xs"> <Row margin="xs">
@ -142,11 +167,16 @@ const ReviewTx = ({
</Paragraph> </Paragraph>
</Row> </Row>
<Row margin="md" align="center"> <Row margin="md" align="center">
<Img src={tx.token.logoUri} height={28} alt={tx.token.name} onError={setImageToPlaceholder} /> <Img src={txToken.logoUri} height={28} alt={txToken.name} onError={setImageToPlaceholder} />
<Paragraph size="md" noMargin className={classes.amount}> <Paragraph size="md" noMargin className={classes.amount}>
{tx.amount} {tx.amount}
{' '} {' '}
{tx.token.symbol} {txToken.symbol}
</Paragraph>
</Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph> </Paragraph>
</Row> </Row>
</Block> </Block>

View File

@ -29,6 +29,9 @@ export const styles = () => ({
amount: { amount: {
marginLeft: sm, marginLeft: sm,
}, },
address: {
marginRight: sm,
},
buttonRow: { buttonRow: {
height: '84px', height: '84px',
justifyContent: 'center', justifyContent: 'center',

View File

@ -1,5 +1,5 @@
// @flow // @flow
import React from 'react' import React, { useState } from 'react'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import InputAdornment from '@material-ui/core/InputAdornment' import InputAdornment from '@material-ui/core/InputAdornment'
@ -10,19 +10,19 @@ import GnoForm from '~/components/forms/GnoForm'
import AddressInput from '~/components/forms/AddressInput' import AddressInput from '~/components/forms/AddressInput'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import ScanQRModal from '~/components/ScanQRModal'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Img from '~/components/layout/Img'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import ButtonLink from '~/components/layout/ButtonLink' import ButtonLink from '~/components/layout/ButtonLink'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import TextareaField from '~/components/forms/TextareaField' import TextareaField from '~/components/forms/TextareaField'
import { import {
composeValidators, composeValidators, mustBeFloat, maxValue, mustBeEthereumContractAddress,
mustBeFloat,
maxValue,
mustBeEthereumContractAddress,
} from '~/components/forms/validator' } from '~/components/forms/validator'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import QRIcon from '~/assets/icons/qrcode.svg'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
@ -30,7 +30,6 @@ type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
safeAddress: string, safeAddress: string,
etherScanLink: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
onSubmit: Function, onSubmit: Function,
@ -41,18 +40,26 @@ const SendCustomTx = ({
classes, classes,
onClose, onClose,
safeAddress, safeAddress,
etherScanLink,
safeName, safeName,
ethBalance, ethBalance,
onSubmit, onSubmit,
initialValues, initialValues,
}: Props) => { }: Props) => {
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const handleSubmit = (values: Object) => { const handleSubmit = (values: Object) => {
if (values.data || values.value) { if (values.data || values.value) {
onSubmit(values) onSubmit(values)
} }
} }
const openQrModal = () => {
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
const formMutators = { const formMutators = {
setMax: (args, state, utils) => { setMax: (args, state, utils) => {
utils.changeValue(state, 'value', () => ethBalance) utils.changeValue(state, 'value', () => ethBalance)
@ -78,15 +85,21 @@ const SendCustomTx = ({
{(...args) => { {(...args) => {
const mutators = args[3] const mutators = args[3]
const handleScan = (value) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setRecipient(scannedAddress)
closeQrModal()
}
return ( return (
<> <>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
<SafeInfo <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -96,7 +109,7 @@ const SendCustomTx = ({
</Col> </Col>
</Row> </Row>
<Row margin="md"> <Row margin="md">
<Col xs={12}> <Col xs={11}>
<AddressInput <AddressInput
name="recipientAddress" name="recipientAddress"
component={TextField} component={TextField}
@ -107,6 +120,18 @@ const SendCustomTx = ({
validators={[mustBeEthereumContractAddress]} validators={[mustBeEthereumContractAddress]}
/> />
</Col> </Col>
<Col xs={1} center="xs" middle="xs" className={classes}>
<Img
src={QRIcon}
className={classes.qrCodeBtn}
role="button"
height={20}
alt="Scan QR"
onClick={() => {
openQrModal()
}}
/>
</Col>
</Row> </Row>
<Row margin="xs"> <Row margin="xs">
<Col between="lg"> <Col between="lg">
@ -124,10 +149,7 @@ const SendCustomTx = ({
name="value" name="value"
component={TextField} component={TextField}
type="text" type="text"
validate={composeValidators( validate={composeValidators(mustBeFloat, maxValue(ethBalance))}
mustBeFloat,
maxValue(ethBalance),
)}
placeholder="Value*" placeholder="Value*"
text="Value*" text="Value*"
className={classes.addressInput} className={classes.addressInput}
@ -164,6 +186,7 @@ const SendCustomTx = ({
Review Review
</Button> </Button>
</Row> </Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
</> </>
) )
}} }}

View File

@ -21,6 +21,9 @@ export const styles = () => ({
height: '35px', height: '35px',
width: '35px', width: '35px',
}, },
qrCodeBtn: {
cursor: 'pointer',
},
formContainer: { formContainer: {
padding: `${md} ${lg}`, padding: `${md} ${lg}`,
}, },

View File

@ -1,5 +1,5 @@
// @flow // @flow
import React, { useEffect, useState } from 'react' import React from 'react'
import { List } from 'immutable' import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem'
@ -22,63 +22,57 @@ type SelectFieldProps = {
} }
type SelectedTokenProps = { type SelectedTokenProps = {
token?: Token, tokenAddress?: string,
classes: Object, classes: Object,
tokens: List<Token>,
} }
const SelectedToken = ({ token, classes }: SelectedTokenProps) => ( const SelectedToken = ({ tokenAddress, tokens, classes }: SelectedTokenProps) => {
<MenuItem className={classes.container}> const token = tokens.find(({ address }) => address === tokenAddress)
{token ? (
<>
<ListItemIcon className={classes.tokenImage}>
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
</ListItemIcon>
<ListItemText
className={classes.tokenData}
primary={token.name}
secondary={`${formatAmount(token.balance)} ${token.symbol}`}
/>
</>
) : (
<Paragraph color="disabled" size="lg" weight="light" style={{ opacity: 0.5 }}>
Select an asset*
</Paragraph>
)}
</MenuItem>
)
const SelectedTokenStyled = withStyles(selectedTokenStyles)(SelectedToken)
type InitialTokenType = Token | string
const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) => {
const [initialToken, setInitialToken] = useState<InitialTokenType>('')
useEffect(() => {
const selectedToken = tokens.find((token) => token.name === initialValue)
setInitialToken(selectedToken || '')
}, [initialValue])
return ( return (
<Field <MenuItem className={classes.container}>
name="token" {token ? (
component={SelectField} <>
classes={{ selectMenu: classes.selectMenu }} <ListItemIcon className={classes.tokenImage}>
validate={required}
renderValue={(token) => <SelectedTokenStyled token={token} />}
initialValue={initialToken}
displayEmpty
>
{tokens.map((token) => (
<MenuItem key={token.address} value={token}>
<ListItemIcon>
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} /> <Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={token.name} secondary={`${formatAmount(token.balance)} ${token.symbol}`} /> <ListItemText
</MenuItem> className={classes.tokenData}
))} primary={token.name}
</Field> secondary={`${formatAmount(token.balance)} ${token.symbol}`}
/>
</>
) : (
<Paragraph color="disabled" size="lg" weight="light" style={{ opacity: 0.5 }}>
Select an asset*
</Paragraph>
)}
</MenuItem>
) )
} }
const SelectedTokenStyled = withStyles(selectedTokenStyles)(SelectedToken)
const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) => (
<Field
name="token"
component={SelectField}
classes={{ selectMenu: classes.selectMenu }}
validate={required}
renderValue={(tokenAddress) => <SelectedTokenStyled tokenAddress={tokenAddress} tokens={tokens} />}
initialValue={initialValue}
displayEmpty
>
{tokens.map((token) => (
<MenuItem key={token.address} value={token.address}>
<ListItemIcon>
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
</ListItemIcon>
<ListItemText primary={token.name} secondary={`${formatAmount(token.balance)} ${token.symbol}`} />
</MenuItem>
))}
</Field>
)
export default withStyles(selectStyles)(TokenSelectField) export default withStyles(selectStyles)(TokenSelectField)

View File

@ -1,5 +1,5 @@
// @flow // @flow
import React from 'react' import React, { useState } from 'react'
import { List } from 'immutable' import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import { OnChange } from 'react-final-form-listeners' import { OnChange } from 'react-final-form-listeners'
@ -13,6 +13,7 @@ import AddressInput from '~/components/forms/AddressInput'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Img from '~/components/layout/Img'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import ButtonLink from '~/components/layout/ButtonLink' import ButtonLink from '~/components/layout/ButtonLink'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
@ -23,14 +24,15 @@ import {
} from '~/components/forms/validator' } from '~/components/forms/validator'
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField' import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import ScanQRModal from '~/components/ScanQRModal'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import QRIcon from '~/assets/icons/qrcode.svg'
import { styles } from './style' import { styles } from './style'
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
safeAddress: string, safeAddress: string,
etherScanLink: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
selectedToken: string, selectedToken: string,
@ -39,11 +41,22 @@ type Props = {
initialValues: Object, initialValues: Object,
} }
const formMutators = {
setMax: (args, state, utils) => {
utils.changeValue(state, 'amount', () => args[0])
},
onTokenChange: (args, state, utils) => {
utils.changeValue(state, 'amount', () => '')
},
setRecipient: (args, state, utils) => {
utils.changeValue(state, 'recipientAddress', () => args[0])
},
}
const SendFunds = ({ const SendFunds = ({
classes, classes,
onClose, onClose,
safeAddress, safeAddress,
etherScanLink,
safeName, safeName,
ethBalance, ethBalance,
tokens, tokens,
@ -51,22 +64,18 @@ const SendFunds = ({
initialValues, initialValues,
onSubmit, onSubmit,
}: Props) => { }: Props) => {
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const handleSubmit = (values) => { const handleSubmit = (values) => {
onSubmit(values) onSubmit(values)
} }
const formMutators = { const openQrModal = () => {
setMax: (args, state, utils) => { setQrModalOpen(true)
const { token } = state.formState.values }
utils.changeValue(state, 'amount', () => token && token.balance) const closeQrModal = () => {
}, setQrModalOpen(false)
onTokenChange: (args, state, utils) => {
utils.changeValue(state, 'amount', () => '')
},
setRecipient: (args, state, utils) => {
utils.changeValue(state, 'recipientAddress', () => args[0])
},
} }
return ( return (
@ -85,16 +94,24 @@ const SendFunds = ({
{(...args) => { {(...args) => {
const formState = args[2] const formState = args[2]
const mutators = args[3] const mutators = args[3]
const { token } = formState.values const { token: tokenAddress } = formState.values
const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress)
const handleScan = (value) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setRecipient(scannedAddress)
closeQrModal()
}
return ( return (
<> <>
<Block className={classes.formContainer}> <Block className={classes.formContainer}>
<SafeInfo <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -104,7 +121,7 @@ const SendFunds = ({
</Col> </Col>
</Row> </Row>
<Row margin="md"> <Row margin="md">
<Col xs={12}> <Col xs={11}>
<AddressInput <AddressInput
name="recipientAddress" name="recipientAddress"
component={TextField} component={TextField}
@ -114,6 +131,18 @@ const SendFunds = ({
fieldMutator={mutators.setRecipient} fieldMutator={mutators.setRecipient}
/> />
</Col> </Col>
<Col xs={1} center="xs" middle="xs" className={classes}>
<Img
src={QRIcon}
className={classes.qrCodeBtn}
role="button"
height={20}
alt="Scan QR"
onClick={() => {
openQrModal()
}}
/>
</Col>
</Row> </Row>
<Row margin="sm"> <Row margin="sm">
<Col> <Col>
@ -125,7 +154,7 @@ const SendFunds = ({
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin> <Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
Amount Amount
</Paragraph> </Paragraph>
<ButtonLink weight="bold" onClick={mutators.setMax}> <ButtonLink weight="bold" onClick={() => mutators.setMax(selectedTokenRecord.balance)}>
Send max Send max
</ButtonLink> </ButtonLink>
</Col> </Col>
@ -140,14 +169,14 @@ const SendFunds = ({
required, required,
mustBeFloat, mustBeFloat,
greaterThan(0), greaterThan(0),
maxValue(token && token.balance), maxValue(selectedTokenRecord && selectedTokenRecord.balance),
)} )}
placeholder="Amount*" placeholder="Amount*"
text="Amount*" text="Amount*"
className={classes.addressInput} className={classes.addressInput}
inputAdornment={ inputAdornment={
token && { selectedTokenRecord && {
endAdornment: <InputAdornment position="end">{token.symbol}</InputAdornment>, endAdornment: <InputAdornment position="end">{selectedTokenRecord.symbol}</InputAdornment>,
} }
} }
/> />
@ -175,6 +204,7 @@ const SendFunds = ({
Review Review
</Button> </Button>
</Row> </Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
</> </>
) )
}} }}

View File

@ -21,6 +21,9 @@ export const styles = () => ({
height: '35px', height: '35px',
width: '35px', width: '35px',
}, },
qrCodeBtn: {
cursor: 'pointer',
},
formContainer: { formContainer: {
padding: `${md} ${lg}`, padding: `${md} ${lg}`,
}, },

View File

@ -18,7 +18,7 @@ export type BalanceRow = SortRow<BalanceData>
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => { export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
const rows = activeTokens.map((token: Token) => ({ const rows = activeTokens.map((token: Token) => ({
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri }, [BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri, address: token.address },
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name, [buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
[BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`, [BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`,
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance), [buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),

View File

@ -43,7 +43,6 @@ type Props = {
activeTokens: List<Token>, activeTokens: List<Token>,
safeAddress: string, safeAddress: string,
safeName: string, safeName: string,
etherScanLink: string,
ethBalance: string, ethBalance: string,
createTransaction: Function, createTransaction: Function,
} }
@ -72,11 +71,11 @@ class Balances extends React.Component<Props, State> {
this.setState(() => ({ [`show${action}`]: false })) this.setState(() => ({ [`show${action}`]: false }))
} }
showSendFunds = (token: Token) => { showSendFunds = (tokenAddress: string) => {
this.setState({ this.setState({
sendFunds: { sendFunds: {
isOpen: true, isOpen: true,
selectedToken: token, selectedToken: tokenAddress,
}, },
}) })
} }
@ -107,7 +106,6 @@ class Balances extends React.Component<Props, State> {
safeAddress, safeAddress,
activeTokens, activeTokens,
safeName, safeName,
etherScanLink,
ethBalance, ethBalance,
createTransaction, createTransaction,
} = this.props } = this.props
@ -176,7 +174,7 @@ class Balances extends React.Component<Props, State> {
size="small" size="small"
color="primary" color="primary"
className={classes.send} className={classes.send}
onClick={() => this.showSendFunds(row.asset.name)} onClick={() => this.showSendFunds(row.asset.address)}
testId="balance-send-btn" testId="balance-send-btn"
> >
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} /> <CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
@ -201,7 +199,6 @@ class Balances extends React.Component<Props, State> {
<SendModal <SendModal
onClose={this.hideSendFunds} onClose={this.hideSendFunds}
isOpen={sendFunds.isOpen} isOpen={sendFunds.isOpen}
etherScanLink={etherScanLink}
safeAddress={safeAddress} safeAddress={safeAddress}
safeName={safeName} safeName={safeName}
ethBalance={ethBalance} ethBalance={ethBalance}

View File

@ -1,6 +1,9 @@
// @flow // @flow
import * as React from 'react' import * as React from 'react'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import {
Switch, Redirect, Route, withRouter,
} from 'react-router-dom'
import Tabs from '@material-ui/core/Tabs' import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab' import Tab from '@material-ui/core/Tab'
import CallMade from '@material-ui/icons/CallMade' import CallMade from '@material-ui/icons/CallMade'
@ -22,7 +25,6 @@ import NoSafe from '~/components/NoSafe'
import { type SelectorProps } from '~/routes/safe/container/selector' import { type SelectorProps } from '~/routes/safe/container/selector'
import { getEtherScanLink } from '~/logic/wallets/getWeb3' import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { border } from '~/theme/variables' import { border } from '~/theme/variables'
import { copyToClipboard } from '~/utils/clipboard'
import { type Actions } from '../container/actions' import { type Actions } from '../container/actions'
import Balances from './Balances' import Balances from './Balances'
import Transactions from './Transactions' import Transactions from './Transactions'
@ -34,10 +36,6 @@ export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn'
export const TRANSACTIONS_TAB_BTN_TEST_ID = 'transactions-tab-btn' export const TRANSACTIONS_TAB_BTN_TEST_ID = 'transactions-tab-btn'
export const SAFE_VIEW_NAME_HEADING_TEST_ID = 'safe-name-heading' export const SAFE_VIEW_NAME_HEADING_TEST_ID = 'safe-name-heading'
type State = {
tabIndex: number,
}
type Props = SelectorProps & type Props = SelectorProps &
Actions & { Actions & {
classes: Object, classes: Object,
@ -48,176 +46,183 @@ type Props = SelectorProps &
onHide: Function, onHide: Function,
showSendFunds: Function, showSendFunds: Function,
hideSendFunds: Function, hideSendFunds: Function,
match: Object,
location: Object,
history: Object,
} }
class Layout extends React.Component<Props, State> { const Layout = (props: Props) => {
constructor(props) { const {
super(props) safe,
this.state = { provider,
tabIndex: 0, network,
} classes,
granted,
tokens,
activeTokens,
createTransaction,
processTransaction,
fetchTransactions,
updateSafe,
transactions,
userAddress,
sendFunds,
showReceive,
onShow,
onHide,
showSendFunds,
hideSendFunds,
match,
location,
} = props
const handleCallToRouter = (_, value) => {
const { history } = props
history.push(value)
} }
handleChange = (event, tabIndex) => { if (!safe) {
this.setState({ tabIndex }) return <NoSafe provider={provider} text="Safe not found" />
} }
copyAddress = () => { const { address, ethBalance, name } = safe
const { safe } = this.props const etherScanLink = getEtherScanLink('address', address)
if (safe.address) { return (
copyToClipboard(safe.address) <>
} <Block className={classes.container} margin="xl">
} <Identicon address={address} diameter={50} />
<Block className={classes.name}>
render() { <Row>
const { <Heading tag="h2" color="primary" testId={SAFE_VIEW_NAME_HEADING_TEST_ID}>
safe, {name}
provider, </Heading>
network, {!granted && <Block className={classes.readonly}>Read Only</Block>}
classes, </Row>
granted, <Block justify="center" className={classes.user}>
tokens, <Paragraph size="md" className={classes.address} color="disabled" noMargin>
activeTokens, {address}
createTransaction, </Paragraph>
processTransaction, <CopyBtn content={address} />
fetchTransactions, <EtherscanBtn type="address" value={address} />
updateSafe,
transactions,
userAddress,
sendFunds,
showReceive,
onShow,
onHide,
showSendFunds,
hideSendFunds,
} = this.props
const { tabIndex } = this.state
if (!safe) {
return <NoSafe provider={provider} text="Safe not found" />
}
const { address, ethBalance, name } = safe
const etherScanLink = getEtherScanLink('address', address)
return (
<>
<Block className={classes.container} margin="xl">
<Identicon address={address} diameter={50} />
<Block className={classes.name}>
<Row>
<Heading tag="h2" color="primary" testId={SAFE_VIEW_NAME_HEADING_TEST_ID}>
{name}
</Heading>
{!granted && <Block className={classes.readonly}>Read Only</Block>}
</Row>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" className={classes.address} onClick={this.copyAddress} title="Click to copy" noMargin>
{address}
</Paragraph>
<CopyBtn content={address} />
<EtherscanBtn type="address" value={address} />
</Block>
</Block>
<Block className={classes.balance}>
<Row align="end" className={classes.actions}>
<Button
variant="contained"
size="small"
color="primary"
className={classes.send}
onClick={() => showSendFunds('Ether')}
disabled={!granted}
>
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
Send
</Button>
<Button
variant="contained"
size="small"
color="primary"
className={classes.receive}
onClick={onShow('Receive')}
>
<CallReceived alt="Receive Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
Receive
</Button>
</Row>
</Block> </Block>
</Block> </Block>
<Row> <Block className={classes.balance}>
<Tabs value={tabIndex} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary"> <Row align="end" className={classes.actions}>
<Tab label="Balances" data-testid={BALANCES_TAB_BTN_TEST_ID} /> <Button
<Tab label="Transactions" data-testid={TRANSACTIONS_TAB_BTN_TEST_ID} /> variant="contained"
<Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TEST_ID} /> size="small"
</Tabs> color="primary"
</Row> className={classes.send}
<Hairline color={border} style={{ marginTop: '-2px' }} /> onClick={() => showSendFunds('Ether')}
{tabIndex === 0 && ( disabled={!granted}
<Balances >
ethBalance={ethBalance} <CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
tokens={tokens} Send
activeTokens={activeTokens} </Button>
granted={granted} <Button
safeAddress={address} variant="contained"
safeName={name} size="small"
etherScanLink={etherScanLink} color="primary"
createTransaction={createTransaction} className={classes.receive}
/> onClick={onShow('Receive')}
)} >
{tabIndex === 1 && ( <CallReceived alt="Receive Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
<Transactions Receive
threshold={safe.threshold} </Button>
owners={safe.owners} </Row>
transactions={transactions} </Block>
fetchTransactions={fetchTransactions} </Block>
safeAddress={address} <Row>
userAddress={userAddress} <Tabs
currentNetwork={network} value={location.pathname}
granted={granted} onChange={handleCallToRouter}
createTransaction={createTransaction} indicatorColor="secondary"
processTransaction={processTransaction} textColor="secondary"
/>
)}
{tabIndex === 2 && (
<Settings
granted={granted}
safeAddress={address}
safeName={name}
etherScanLink={etherScanLink}
updateSafe={updateSafe}
threshold={safe.threshold}
owners={safe.owners}
network={network}
userAddress={userAddress}
createTransaction={createTransaction}
/>
)}
<SendModal
onClose={hideSendFunds}
isOpen={sendFunds.isOpen}
etherScanLink={etherScanLink}
safeAddress={address}
safeName={name}
ethBalance={ethBalance}
tokens={activeTokens}
selectedToken={sendFunds.selectedToken}
createTransaction={createTransaction}
activeScreenType="chooseTxType"
/>
<Modal
title="Receive Tokens"
description="Receive Tokens Form"
handleClose={onHide('Receive')}
open={showReceive}
paperClassName={classes.receiveModal}
> >
<Receive safeName={name} safeAddress={address} etherScanLink={etherScanLink} onClose={onHide('Receive')} /> <Tab label="Balances" value={`${match.url}/balances`} data-testid={BALANCES_TAB_BTN_TEST_ID} />
</Modal> <Tab label="Transactions" value={`${match.url}/transactions`} data-testid={TRANSACTIONS_TAB_BTN_TEST_ID} />
</> <Tab label="Settings" value={`${match.url}/settings`} data-testid={SETTINGS_TAB_BTN_TEST_ID} />
) </Tabs>
} </Row>
<Hairline color={border} style={{ marginTop: '-2px' }} />
<Switch>
<Route
exact
path={`${match.path}/balances`}
render={() => (
<Balances
ethBalance={ethBalance}
tokens={tokens}
activeTokens={activeTokens}
granted={granted}
safeAddress={address}
safeName={name}
createTransaction={createTransaction}
/>
)}
/>
<Route
exact
path={`${match.path}/transactions`}
render={() => (
<Transactions
threshold={safe.threshold}
owners={safe.owners}
transactions={transactions}
fetchTransactions={fetchTransactions}
safeAddress={address}
userAddress={userAddress}
currentNetwork={network}
granted={granted}
createTransaction={createTransaction}
processTransaction={processTransaction}
/>
)}
/>
<Route
exact
path={`${match.path}/settings`}
render={() => (
<Settings
granted={granted}
safeAddress={address}
safeName={name}
etherScanLink={etherScanLink}
updateSafe={updateSafe}
threshold={safe.threshold}
owners={safe.owners}
network={network}
userAddress={userAddress}
createTransaction={createTransaction}
/>
)}
/>
<Redirect to={`${match.path}/balances`} />
</Switch>
<SendModal
onClose={hideSendFunds}
isOpen={sendFunds.isOpen}
safeAddress={address}
safeName={name}
ethBalance={ethBalance}
tokens={activeTokens}
selectedToken={sendFunds.selectedToken}
createTransaction={createTransaction}
activeScreenType="chooseTxType"
/>
<Modal
title="Receive Tokens"
description="Receive Tokens Form"
handleClose={onHide('Receive')}
open={showReceive}
paperClassName={classes.receiveModal}
>
<Receive safeName={name} safeAddress={address} onClose={onHide('Receive')} />
</Modal>
</>
)
} }
export default withStyles(styles)(Layout) export default withStyles(styles)(withRouter(Layout))

View File

@ -12,7 +12,7 @@ import GnoForm from '~/components/forms/GnoForm'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
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 { getNofiticationsFromTxType, showSnackbar } from '~/logic/notifications' import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { styles } from './style' import { styles } from './style'
@ -36,7 +36,7 @@ const ChangeSafeName = (props: Props) => {
const handleSubmit = (values) => { const handleSubmit = (values) => {
updateSafe({ address: safeAddress, name: values.safeName }) updateSafe({ address: safeAddress, name: values.safeName })
const notification = getNofiticationsFromTxType(TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX) const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX)
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar) showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
} }

View File

@ -16,6 +16,7 @@ const styles = () => ({
width: '775px', width: '775px',
minHeight: '500px', minHeight: '500px',
position: 'static', position: 'static',
height: 'auto',
}, },
}) })
@ -146,6 +147,7 @@ const AddOwner = ({
safeName={safeName} safeName={safeName}
owners={owners} owners={owners}
values={values} values={values}
safeAddress={safeAddress}
onClickBack={onClickBack} onClickBack={onClickBack}
onSubmit={onAddOwner} onSubmit={onAddOwner}
/> />

View File

@ -1,13 +1,14 @@
// @flow // @flow
import React from 'react' import React, { useState, useEffect } from 'react'
import { List } from 'immutable' import { List } from 'immutable'
import classNames from 'classnames' import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link' import EtherscanBtn from '~/components/EtherscanBtn'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import CopyBtn from '~/components/CopyBtn'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
@ -15,17 +16,13 @@ import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner' import type { Owner } from '~/routes/safe/store/models/owner'
import { getEtherScanLink } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables' import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style' import { styles } from './style'
export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn' export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
@ -34,11 +31,39 @@ type Props = {
values: Object, values: Object,
onClickBack: Function, onClickBack: Function,
onSubmit: Function, onSubmit: Function,
safeAddress: string,
} }
const ReviewAddOwner = ({ const ReviewAddOwner = ({
classes, onClose, safeName, owners, values, onClickBack, onSubmit, classes, onClose, safeName, owners, values, onClickBack, onSubmit, safeAddress,
}: Props) => { }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const txData = safeInstance.contract.methods
.addOwnerWithThreshold(values.ownerAddress, values.threshold)
.encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
const handleSubmit = () => { const handleSubmit = () => {
onSubmit() onSubmit()
} }
@ -76,12 +101,7 @@ const ReviewAddOwner = ({
Any transaction requires the confirmation of: Any transaction requires the confirmation of:
</Paragraph> </Paragraph>
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}> <Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{values.threshold} {`${values.threshold} out of ${owners.size + 1} owner(s)`}
{' '}
out of
{owners.size + 1}
{' '}
owner(s)
</Paragraph> </Paragraph>
</Block> </Block>
</Block> </Block>
@ -89,9 +109,7 @@ const ReviewAddOwner = ({
<Col xs={8} layout="column" className={classes.owners}> <Col xs={8} layout="column" className={classes.owners}>
<Row className={classes.ownersTitle}> <Row className={classes.ownersTitle}>
<Paragraph size="lg" color="primary" noMargin> <Paragraph size="lg" color="primary" noMargin>
{owners.size + 1} {`${owners.size + 1} Safe owner(s)`}
{' '}
Safe owner(s)
</Paragraph> </Paragraph>
</Row> </Row>
<Hairline /> <Hairline />
@ -107,12 +125,11 @@ const ReviewAddOwner = ({
{owner.name} {owner.name}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{owner.address} {owner.address}
</Paragraph> </Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', owner.address)} target="_blank"> <CopyBtn content={owner.address} />
<OpenInNew style={openIconStyle} /> <EtherscanBtn type="address" value={owner.address} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -136,16 +153,11 @@ const ReviewAddOwner = ({
{values.ownerName} {values.ownerName}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{values.ownerAddress} {values.ownerAddress}
</Paragraph> </Paragraph>
<Link <CopyBtn content={values.ownerAddress} />
className={classes.open} <EtherscanBtn type="address" value={values.ownerAddress} />
to={getEtherScanLink('address', values.ownerAddress)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -155,6 +167,14 @@ const ReviewAddOwner = ({
</Row> </Row>
</Block> </Block>
<Hairline /> <Hairline />
<Block className={classes.gasCostsContainer}>
<Paragraph>
You&apos;re about to create a transaction and will have to confirm it with your currently connected wallet.
<br />
{`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClickBack}> <Button minWidth={140} minHeight={42} onClick={onClickBack}>
Back Back

View File

@ -39,7 +39,7 @@ export const styles = () => ({
}, },
details: { details: {
padding: lg, padding: lg,
borderRight: `solid 1px ${border}`, borderRight: `solid 2px ${border}`,
height: '100%', height: '100%',
}, },
owners: { owners: {
@ -49,6 +49,9 @@ export const styles = () => ({
ownersTitle: { ownersTitle: {
padding: lg, padding: lg,
}, },
address: {
marginRight: sm,
},
owner: { owner: {
padding: sm, padding: sm,
alignItems: 'center', alignItems: 'center',
@ -75,4 +78,9 @@ export const styles = () => ({
cursor: 'pointer', cursor: 'pointer',
}, },
}, },
gasCostsContainer: {
padding: `0 ${lg}`,
textAlign: 'center',
width: '100%',
},
}) })

View File

@ -3,10 +3,10 @@ import React from 'react'
import { withSnackbar } from 'notistack' import { withSnackbar } from 'notistack'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import OpenInNew from '@material-ui/icons/OpenInNew'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
@ -16,21 +16,15 @@ import TextField from '~/components/forms/TextField'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import { composeValidators, required, minMaxLength } from '~/components/forms/validator' import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
import { getNofiticationsFromTxType, showSnackbar } from '~/logic/notifications' import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import Modal from '~/components/Modal' import Modal from '~/components/Modal'
import { styles } from './style' import { styles } from './style'
import { secondary } from '~/theme/variables' import { sm } from '~/theme/variables'
export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input' export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input'
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn' export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
@ -57,7 +51,7 @@ const EditOwnerComponent = ({
const handleSubmit = (values) => { const handleSubmit = (values) => {
editSafeOwner({ safeAddress, ownerAddress, ownerName: values.ownerName }) editSafeOwner({ safeAddress, ownerAddress, ownerName: values.ownerName })
const notification = getNofiticationsFromTxType(TX_NOTIFICATION_TYPES.OWNER_NAME_CHANGE_TX) const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.OWNER_NAME_CHANGE_TX)
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar) showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
onClose() onClose()
@ -100,12 +94,11 @@ const EditOwnerComponent = ({
<Row> <Row>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Identicon address={ownerAddress} diameter={32} /> <Identicon address={ownerAddress} diameter={32} />
<Paragraph style={{ marginLeft: 10 }} size="md" color="disabled" noMargin> <Paragraph style={{ marginLeft: sm, marginRight: sm }} size="md" color="disabled" noMargin>
{ownerAddress} {ownerAddress}
</Paragraph> </Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank"> <CopyBtn content={safeAddress} />
<OpenInNew style={openIconStyle} /> <EtherscanBtn type="address" value={safeAddress} />
</Link>
</Block> </Block>
</Row> </Row>
</Block> </Block>
@ -114,7 +107,14 @@ const EditOwnerComponent = ({
<Button minWidth={140} minHeight={42} onClick={onClose}> <Button minWidth={140} minHeight={42} onClick={onClose}>
Cancel Cancel
</Button> </Button>
<Button type="submit" variant="contained" minWidth={140} minHeight={42} color="primary" testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}> <Button
type="submit"
variant="contained"
minWidth={140}
minHeight={42}
color="primary"
testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}
>
Save Save
</Button> </Button>
</Row> </Row>

View File

@ -16,6 +16,7 @@ const styles = () => ({
width: '775px', width: '775px',
minHeight: '500px', minHeight: '500px',
position: 'static', position: 'static',
height: 'auto',
}, },
}) })
@ -168,6 +169,7 @@ const RemoveOwner = ({
ownerName={ownerName} ownerName={ownerName}
onClickBack={onClickBack} onClickBack={onClickBack}
onSubmit={onRemoveOwner} onSubmit={onRemoveOwner}
safeAddress={safeAddress}
/> />
)} )}
</> </>

View File

@ -4,26 +4,19 @@ import classNames from 'classnames/bind'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import Link from '~/components/layout/Link' import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { styles } from './style' import { styles } from './style'
import { secondary } from '~/theme/variables'
export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn' export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
@ -65,12 +58,11 @@ const CheckOwner = ({
{ownerName} {ownerName}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{ownerAddress} {ownerAddress}
</Paragraph> </Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank"> <CopyBtn content={ownerAddress} />
<OpenInNew style={openIconStyle} /> <EtherscanBtn type="address" value={ownerAddress} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>

View File

@ -19,6 +19,9 @@ export const styles = () => ({
manage: { manage: {
fontSize: '24px', fontSize: '24px',
}, },
address: {
marginRight: sm,
},
closeIcon: { closeIcon: {
height: '35px', height: '35px',
width: '35px', width: '35px',

View File

@ -1,13 +1,13 @@
// @flow // @flow
import React from 'react' import React, { useState, useEffect } from 'react'
import { List } from 'immutable' import { List } from 'immutable'
import classNames from 'classnames' import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
@ -15,17 +15,14 @@ import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner' import type { Owner } from '~/routes/safe/store/models/owner'
import { getEtherScanLink } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables' import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style' import { styles } from './style'
export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn' export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
@ -36,6 +33,7 @@ type Props = {
ownerName: string, ownerName: string,
onClickBack: Function, onClickBack: Function,
onSubmit: Function, onSubmit: Function,
safeAddress: string,
} }
const ReviewRemoveOwner = ({ const ReviewRemoveOwner = ({
@ -48,10 +46,36 @@ const ReviewRemoveOwner = ({
ownerName, ownerName,
onClickBack, onClickBack,
onSubmit, onSubmit,
safeAddress,
}: Props) => { }: Props) => {
const handleSubmit = () => { const [gasCosts, setGasCosts] = useState<string>('< 0.001')
onSubmit()
} useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const safeOwners = await gnosisSafe.getOwners()
const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase())
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
const txData = gnosisSafe.contract.methods.removeOwner(prevAddress, ownerAddress, values.threshold).encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
return ( return (
<> <>
@ -87,12 +111,7 @@ const ReviewRemoveOwner = ({
Any transaction requires the confirmation of: Any transaction requires the confirmation of:
</Paragraph> </Paragraph>
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}> <Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{values.threshold} {`${values.threshold} out of ${owners.size - 1} owner(s)`}
{' '}
out of
{owners.size - 1}
{' '}
owner(s)
</Paragraph> </Paragraph>
</Block> </Block>
</Block> </Block>
@ -100,9 +119,7 @@ const ReviewRemoveOwner = ({
<Col xs={8} layout="column" className={classes.owners}> <Col xs={8} layout="column" className={classes.owners}>
<Row className={classes.ownersTitle}> <Row className={classes.ownersTitle}>
<Paragraph size="lg" color="primary" noMargin> <Paragraph size="lg" color="primary" noMargin>
{owners.size - 1} {`${owners.size - 1} Safe owner(s)`}
{' '}
Safe owner(s)
</Paragraph> </Paragraph>
</Row> </Row>
<Hairline /> <Hairline />
@ -119,16 +136,11 @@ const ReviewRemoveOwner = ({
{owner.name} {owner.name}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{owner.address} {owner.address}
</Paragraph> </Paragraph>
<Link <CopyBtn content={owner.address} />
className={classes.open} <EtherscanBtn type="address" value={owner.address} />
to={getEtherScanLink('address', owner.address)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -153,16 +165,11 @@ const ReviewRemoveOwner = ({
{ownerName} {ownerName}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{ownerAddress} {ownerAddress}
</Paragraph> </Paragraph>
<Link <CopyBtn content={ownerAddress} />
className={classes.open} <EtherscanBtn type="address" value={ownerAddress} />
to={getEtherScanLink('address', ownerAddress)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -172,13 +179,21 @@ const ReviewRemoveOwner = ({
</Row> </Row>
</Block> </Block>
<Hairline /> <Hairline />
<Block className={classes.gasCostsContainer}>
<Paragraph>
You&apos;re about to create a transaction and will have to confirm it with your currently connected wallet.
<br />
{`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClickBack}> <Button minWidth={140} minHeight={42} onClick={onClickBack}>
Back Back
</Button> </Button>
<Button <Button
type="submit" type="submit"
onClick={handleSubmit} onClick={onSubmit}
variant="contained" variant="contained"
minHeight={42} minHeight={42}
minWidth={140} minWidth={140}

View File

@ -49,6 +49,9 @@ export const styles = () => ({
ownersTitle: { ownersTitle: {
padding: lg, padding: lg,
}, },
address: {
marginRight: sm,
},
owner: { owner: {
padding: sm, padding: sm,
alignItems: 'center', alignItems: 'center',
@ -75,4 +78,9 @@ export const styles = () => ({
cursor: 'pointer', cursor: 'pointer',
}, },
}, },
gasCostsContainer: {
padding: `0 ${lg}`,
textAlign: 'center',
width: '100%',
},
}) })

View File

@ -15,6 +15,7 @@ const styles = () => ({
width: '775px', width: '775px',
minHeight: '500px', minHeight: '500px',
position: 'static', position: 'static',
height: 'auto',
}, },
}) })
@ -155,6 +156,7 @@ const ReplaceOwner = ({
onClickBack={onClickBack} onClickBack={onClickBack}
onSubmit={onReplaceOwner} onSubmit={onReplaceOwner}
threshold={threshold} threshold={threshold}
safeAddress={safeAddress}
/> />
)} )}
</> </>

View File

@ -5,7 +5,8 @@ import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import OpenInNew from '@material-ui/icons/OpenInNew' import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import GnoForm from '~/components/forms/GnoForm' import GnoForm from '~/components/forms/GnoForm'
@ -17,24 +18,16 @@ import Hairline from '~/components/layout/Hairline'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { type Owner } from '~/routes/safe/store/models/owner' import { type Owner } from '~/routes/safe/store/models/owner'
import { import {
composeValidators, required, minMaxLength, uniqueAddress, composeValidators, required, minMaxLength, uniqueAddress,
} from '~/components/forms/validator' } from '~/components/forms/validator'
import { styles } from './style' import { styles } from './style'
import { secondary } from '~/theme/variables'
export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input' export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input'
export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid' export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid'
export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn' export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
const formMutators = { const formMutators = {
setOwnerAddress: (args, state, utils) => { setOwnerAddress: (args, state, utils) => {
utils.changeValue(state, 'ownerAddress', () => args[0]) utils.changeValue(state, 'ownerAddress', () => args[0])
@ -96,16 +89,11 @@ const OwnerForm = ({
{ownerName} {ownerName}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" className={classes.address} color="disabled" noMargin>
{ownerAddress} {ownerAddress}
</Paragraph> </Paragraph>
<Link <CopyBtn content={ownerAddress} />
className={classes.open} <EtherscanBtn type="address" value={ownerAddress} />
to={getEtherScanLink('address', ownerAddress)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>

View File

@ -16,6 +16,9 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
lineHeight: 'normal', lineHeight: 'normal',
}, },
address: {
marginRight: sm,
},
manage: { manage: {
fontSize: '24px', fontSize: '24px',
}, },

View File

@ -1,13 +1,13 @@
// @flow // @flow
import React from 'react' import React, { useState, useEffect } from 'react'
import { List } from 'immutable' import { List } from 'immutable'
import classNames from 'classnames' import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon' import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
@ -15,17 +15,14 @@ import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline' import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner' import type { Owner } from '~/routes/safe/store/models/owner'
import { getEtherScanLink } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables' import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
import { styles } from './style' import { styles } from './style'
export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn' export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
@ -37,6 +34,7 @@ type Props = {
onClickBack: Function, onClickBack: Function,
onSubmit: Function, onSubmit: Function,
threshold: string, threshold: string,
safeAddress: string,
} }
const ReviewRemoveOwner = ({ const ReviewRemoveOwner = ({
@ -47,13 +45,37 @@ const ReviewRemoveOwner = ({
values, values,
ownerAddress, ownerAddress,
ownerName, ownerName,
safeAddress,
onClickBack, onClickBack,
threshold, threshold,
onSubmit, onSubmit,
}: Props) => { }: Props) => {
const handleSubmit = () => { const [gasCosts, setGasCosts] = useState<string>('< 0.001')
onSubmit()
} useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const safeOwners = await gnosisSafe.getOwners()
const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase())
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
const txData = gnosisSafe.contract.methods.swapOwner(prevAddress, ownerAddress, values.ownerAddress).encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
return ( return (
<> <>
@ -89,13 +111,7 @@ const ReviewRemoveOwner = ({
Any transaction requires the confirmation of: Any transaction requires the confirmation of:
</Paragraph> </Paragraph>
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}> <Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{threshold} {`${threshold} out of ${owners.size} owner(s)`}
{' '}
out of
{' '}
{owners.size}
{' '}
owner(s)
</Paragraph> </Paragraph>
</Block> </Block>
</Block> </Block>
@ -103,9 +119,7 @@ const ReviewRemoveOwner = ({
<Col xs={8} layout="column" className={classes.owners}> <Col xs={8} layout="column" className={classes.owners}>
<Row className={classes.ownersTitle}> <Row className={classes.ownersTitle}>
<Paragraph size="lg" color="primary" noMargin> <Paragraph size="lg" color="primary" noMargin>
{owners.size} {`${owners.size} Safe owner(s)`}
{' '}
Safe owner(s)
</Paragraph> </Paragraph>
</Row> </Row>
<Hairline /> <Hairline />
@ -122,16 +136,11 @@ const ReviewRemoveOwner = ({
{owner.name} {owner.name}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{owner.address} {owner.address}
</Paragraph> </Paragraph>
<Link <CopyBtn content={owner.address} />
className={classes.open} <EtherscanBtn type="address" value={owner.address} />
to={getEtherScanLink('address', owner.address)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -156,12 +165,11 @@ const ReviewRemoveOwner = ({
{ownerName} {ownerName}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{ownerAddress} {ownerAddress}
</Paragraph> </Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank"> <CopyBtn content={ownerAddress} />
<OpenInNew style={openIconStyle} /> <EtherscanBtn type="address" value={ownerAddress} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -182,12 +190,11 @@ const ReviewRemoveOwner = ({
{values.ownerName} {values.ownerName}
</Paragraph> </Paragraph>
<Block justify="center" className={classes.user}> <Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin> <Paragraph size="md" color="disabled" className={classes.address} noMargin>
{values.ownerAddress} {values.ownerAddress}
</Paragraph> </Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', values.ownerAddress)} target="_blank"> <CopyBtn content={values.ownerAddress} />
<OpenInNew style={openIconStyle} /> <EtherscanBtn type="address" value={values.ownerAddress} />
</Link>
</Block> </Block>
</Block> </Block>
</Col> </Col>
@ -197,13 +204,21 @@ const ReviewRemoveOwner = ({
</Row> </Row>
</Block> </Block>
<Hairline /> <Hairline />
<Block className={classes.gasCostsContainer}>
<Paragraph>
You&apos;re about to create a transaction and will have to confirm it with your currently connected wallet.
<br />
{`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClickBack}> <Button minWidth={140} minHeight={42} onClick={onClickBack}>
Back Back
</Button> </Button>
<Button <Button
type="submit" type="submit"
onClick={handleSubmit} onClick={onSubmit}
variant="contained" variant="contained"
minHeight={42} minHeight={42}
minWidth={140} minWidth={140}

View File

@ -22,6 +22,9 @@ export const styles = () => ({
manage: { manage: {
fontSize: '24px', fontSize: '24px',
}, },
address: {
marginRight: sm,
},
closeIcon: { closeIcon: {
height: '35px', height: '35px',
width: '35px', width: '35px',
@ -80,4 +83,9 @@ export const styles = () => ({
cursor: 'pointer', cursor: 'pointer',
}, },
}, },
gasCostsContainer: {
padding: `0 ${lg}`,
textAlign: 'center',
width: '100%',
},
}) })

View File

@ -1,5 +1,5 @@
// @flow // @flow
import React from 'react' import React, { useState, useEffect } from 'react'
import { List } from 'immutable' import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
@ -18,11 +18,16 @@ import Block from '~/components/layout/Block'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col' import Col from '~/components/layout/Col'
import type { Owner } from '~/routes/safe/store/models/owner' import type { Owner } from '~/routes/safe/store/models/owner'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style' import { styles } from './style'
type Props = { type Props = {
onClose: () => void, onClose: () => void,
classes: Object, classes: Object,
safeAddress: string,
threshold: number, threshold: number,
owners: List<Owner>, owners: List<Owner>,
onChangeThreshold: Function, onChangeThreshold: Function,
@ -31,13 +36,38 @@ type Props = {
const THRESHOLD_FIELD_NAME = 'threshold' const THRESHOLD_FIELD_NAME = 'threshold'
const ChangeThreshold = ({ const ChangeThreshold = ({
onClose, owners, threshold, classes, onChangeThreshold, onClose, owners, threshold, classes, onChangeThreshold, safeAddress,
}: Props) => { }: Props) => {
const handleSubmit = async (values) => { const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGasCosts = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const txData = safeInstance.contract.methods.changeThreshold('1').encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGasCosts()
return () => {
isCurrent = false
}
}, [])
const handleSubmit = (values) => {
const newThreshold = values[THRESHOLD_FIELD_NAME] const newThreshold = values[THRESHOLD_FIELD_NAME]
await onChangeThreshold(newThreshold)
onClose() onClose()
onChangeThreshold(newThreshold)
} }
return ( return (
@ -62,15 +92,13 @@ const ChangeThreshold = ({
</Paragraph> </Paragraph>
</Row> </Row>
<Row> <Row>
<Paragraph weight="bolder"> <Paragraph weight="bolder">Any transaction requires the confirmation of:</Paragraph>
Any transaction requires the confirmation of:
</Paragraph>
</Row> </Row>
<Row margin="xl" align="center" className={classes.inputRow}> <Row margin="xl" align="center" className={classes.inputRow}>
<Col xs={2}> <Col xs={2}>
<Field <Field
name={THRESHOLD_FIELD_NAME} name={THRESHOLD_FIELD_NAME}
render={(props) => ( render={(props: Object) => (
<> <>
<SelectField {...props} disableError> <SelectField {...props} disableError>
{[...Array(Number(owners.size))].map((x, index) => ( {[...Array(Number(owners.size))].map((x, index) => (
@ -92,14 +120,15 @@ const ChangeThreshold = ({
</Col> </Col>
<Col xs={10}> <Col xs={10}>
<Paragraph size="lg" color="primary" noMargin className={classes.ownersText}> <Paragraph size="lg" color="primary" noMargin className={classes.ownersText}>
out of {`out of ${owners.size} owner(s)`}
{' '}
{owners.size}
{' '}
owner(s)
</Paragraph> </Paragraph>
</Col> </Col>
</Row> </Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block> </Block>
<Hairline style={{ position: 'absolute', bottom: 85 }} /> <Hairline style={{ position: 'absolute', bottom: 85 }} />
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>

View File

@ -98,6 +98,7 @@ const ThresholdSettings = ({
onClose={toggleModal} onClose={toggleModal}
owners={owners} owners={owners}
threshold={threshold} threshold={threshold}
safeAddress={safeAddress}
onChangeThreshold={onChangeThreshold} onChangeThreshold={onChangeThreshold}
/> />
</Modal> </Modal>

View File

@ -1,5 +1,5 @@
// @flow // @flow
import React, { useState } from 'react' import React, { useState, useEffect } from 'react'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
@ -13,6 +13,9 @@ import Row from '~/components/layout/Row'
import Bold from '~/components/layout/Bold' import Bold from '~/components/layout/Bold'
import Block from '~/components/layout/Block' import Block from '~/components/layout/Block'
import Paragraph from '~/components/layout/Paragraph' import Paragraph from '~/components/layout/Paragraph'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { type Transaction } from '~/routes/safe/store/models/transaction' import { type Transaction } from '~/routes/safe/store/models/transaction'
import { styles } from './style' import { styles } from './style'
@ -61,10 +64,39 @@ const ApproveTxModal = ({
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
}: Props) => { }: Props) => {
const [approveAndExecute, setApproveAndExecute] = useState<boolean>(false) const [approveAndExecute, setApproveAndExecute] = useState<boolean>(true)
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
const { title, description } = getModalTitleAndDescription(thresholdReached) const { title, description } = getModalTitleAndDescription(thresholdReached)
const oneConfirmationLeft = tx.confirmations.size + 1 === threshold const oneConfirmationLeft = tx.confirmations.size + 1 === threshold
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(
safeAddress,
tx.recipient,
tx.data,
tx,
approveAndExecute ? userAddress : undefined,
)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [approveAndExecute])
const handleExecuteCheckbox = () => setApproveAndExecute((prevApproveAndExecute) => !prevApproveAndExecute) const handleExecuteCheckbox = () => setApproveAndExecute((prevApproveAndExecute) => !prevApproveAndExecute)
const approveTx = () => { const approveTx = () => {
@ -102,8 +134,8 @@ const ApproveTxModal = ({
{!thresholdReached && oneConfirmationLeft && ( {!thresholdReached && oneConfirmationLeft && (
<> <>
<Paragraph color="error"> <Paragraph color="error">
Approving transaction does not execute it immediately. If you want to approve and execute the Approving this transaction executes it right away. If you want approve but execute the transaction
transaction right away, click on checkbox below. manually later, click on the checkbox below.
</Paragraph> </Paragraph>
<FormControlLabel <FormControlLabel
control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />} control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />}
@ -112,6 +144,13 @@ const ApproveTxModal = ({
</> </>
)} )}
</Row> </Row>
<Row>
<Paragraph>
{`You're about to ${
approveAndExecute ? 'execute' : 'approve'
} a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block> </Block>
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClose}> <Button minWidth={140} minHeight={42} onClick={onClose}>

View File

@ -1,5 +1,5 @@
// @flow // @flow
import React from 'react' import React, { useEffect, useState } from 'react'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
@ -14,6 +14,9 @@ import Paragraph from '~/components/layout/Paragraph'
import { type Transaction } from '~/routes/safe/store/models/transaction' import { type Transaction } from '~/routes/safe/store/models/transaction'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style' import { styles } from './style'
type Props = { type Props = {
@ -37,6 +40,29 @@ const CancelTxModal = ({
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
}: Props) => { }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGasCosts = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, EMPTY_DATA)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGasCosts()
return () => {
isCurrent = false
}
}, [])
const sendReplacementTransaction = () => { const sendReplacementTransaction = () => {
createTransaction( createTransaction(
safeAddress, safeAddress,
@ -79,6 +105,11 @@ const CancelTxModal = ({
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold> <Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
</Paragraph> </Paragraph>
</Row> </Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block> </Block>
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClose}> <Button minWidth={140} minHeight={42} onClick={onClose}>

View File

@ -116,23 +116,27 @@ const ExpandedTx = ({
/> />
</Row> </Row>
</Block> </Block>
<CancelTxModal {openModal === 'cancelTx' && (
isOpen={openModal === 'cancelTx'} <CancelTxModal
createTransaction={createTransaction} isOpen
onClose={closeModal} createTransaction={createTransaction}
tx={tx} onClose={closeModal}
safeAddress={safeAddress} tx={tx}
/> safeAddress={safeAddress}
<ApproveTxModal />
isOpen={openModal === 'approveTx'} )}
processTransaction={processTransaction} {openModal === 'approveTx' && (
onClose={closeModal} <ApproveTxModal
tx={tx} isOpen
userAddress={userAddress} processTransaction={processTransaction}
safeAddress={safeAddress} onClose={closeModal}
threshold={threshold} tx={tx}
thresholdReached={thresholdReached} userAddress={userAddress}
/> safeAddress={safeAddress}
threshold={threshold}
thresholdReached={thresholdReached}
/>
)}
</> </>
) )
} }

View File

@ -1,5 +1,6 @@
// @flow // @flow
import { format, getTime, parseISO } from 'date-fns' import { format, getTime, parseISO } from 'date-fns'
import { BigNumber } from 'bignumber.js'
import { List } from 'immutable' import { List } from 'immutable'
import { type Transaction } from '~/routes/safe/store/models/transaction' import { type Transaction } from '~/routes/safe/store/models/transaction'
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting' import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
@ -32,7 +33,8 @@ export const getTxAmount = (tx: Transaction) => {
let txAmount = 'n/a' let txAmount = 'n/a'
if (tx.isTokenTransfer && tx.decodedParams) { if (tx.isTokenTransfer && tx.decodedParams) {
txAmount = `${fromWei(toBN(tx.decodedParams.value), 'ether')} ${tx.symbol}` const tokenDecimals = tx.decimals.toNumber ? tx.decimals.toNumber() : tx.decimals
txAmount = `${new BigNumber(tx.decodedParams.value).div(10 ** tokenDecimals).toString()} ${tx.symbol}`
} else if (Number(tx.value) > 0) { } else if (Number(tx.value) > 0) {
txAmount = `${fromWei(toBN(tx.value), 'ether')} ${tx.symbol}` txAmount = `${fromWei(toBN(tx.value), 'ether')} ${tx.symbol}`
} }

View File

@ -115,8 +115,9 @@ const extendedTransactionsSelector: Selector<GlobalState, RouterProps, List<Tran
let replacementTransaction let replacementTransaction
if (!tx.isExecuted) { if (!tx.isExecuted) {
replacementTransaction = transactions.findLast( replacementTransaction = transactions.findLast(
(transaction) => transaction.nonce === tx.nonce (transaction) => (transaction.nonce === tx.nonce
&& isAfter(parseISO(transaction.submissionDate), parseISO(tx.submissionDate)), && isAfter(parseISO(transaction.submissionDate), parseISO(tx.submissionDate)))
|| transaction.nonce > tx.nonce,
) )
if (replacementTransaction) { if (replacementTransaction) {
extendedTx = tx.set('cancelled', true) extendedTx = tx.set('cancelled', true)

View File

@ -1,5 +1,6 @@
// @flow // @flow
import type { Dispatch as ReduxDispatch, GetState } from 'redux' import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import { push } from 'connected-react-router'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { userAccountSelector } from '~/logic/wallets/store/selectors' import { userAccountSelector } from '~/logic/wallets/store/selectors'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
@ -17,11 +18,12 @@ import {
import { import {
type Notification, type Notification,
type NotificationsQueue, type NotificationsQueue,
getNofiticationsFromTxType, getNotificationsFromTxType,
showSnackbar, showSnackbar,
} from '~/logic/notifications' } from '~/logic/notifications'
import { getErrorMessage } from '~/test/utils/ethereumErrors' import { getErrorMessage } from '~/test/utils/ethereumErrors'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses' import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
import { SAFELIST_ADDRESS } from '~/routes/routes'
const createTransaction = ( const createTransaction = (
safeAddress: string, safeAddress: string,
@ -35,6 +37,8 @@ const createTransaction = (
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => { ) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
const state: GlobalState = getState() const state: GlobalState = getState()
dispatch(push(`${SAFELIST_ADDRESS}/${safeAddress}/transactions`))
const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const from = userAccountSelector(state) const from = userAccountSelector(state)
const threshold = await safeInstance.getThreshold() const threshold = await safeInstance.getThreshold()
@ -47,7 +51,7 @@ const createTransaction = (
'', '',
)}000000000000000000000000000000000000000000000000000000000000000001` )}000000000000000000000000000000000000000000000000000000000000000001`
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction) const notificationsQueue: NotificationsQueue = getNotificationsFromTxType(notifiedTransaction)
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar) const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
let pendingExecutionKey let pendingExecutionKey
@ -99,6 +103,7 @@ const createTransaction = (
if (isExecution) { if (isExecution) {
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar) showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
} }
dispatch(fetchTransactions(safeAddress))
return receipt.transactionHash return receipt.transactionHash
}) })
@ -114,8 +119,6 @@ const createTransaction = (
console.error(`Error executing the TX: ${errMsg}`) console.error(`Error executing the TX: ${errMsg}`)
} }
dispatch(fetchTransactions(safeAddress))
return txHash return txHash
} }

View File

@ -16,6 +16,7 @@ import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
import { isTokenTransfer } from '~/logic/tokens/utils/tokenHelpers' import { isTokenTransfer } from '~/logic/tokens/utils/tokenHelpers'
import { TX_TYPE_EXECUTION } from '~/logic/safe/transactions' import { TX_TYPE_EXECUTION } from '~/logic/safe/transactions'
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds' import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
let web3 let web3
@ -59,8 +60,8 @@ export const buildTransactionFrom = async (
) )
const modifySettingsTx = tx.to === safeAddress && Number(tx.value) === 0 && !!tx.data const modifySettingsTx = tx.to === safeAddress && Number(tx.value) === 0 && !!tx.data
const cancellationTx = tx.to === safeAddress && Number(tx.value) === 0 && !tx.data const cancellationTx = tx.to === safeAddress && Number(tx.value) === 0 && !tx.data
const customTx = tx.to !== safeAddress && !!tx.data
const isSendTokenTx = await isTokenTransfer(tx.data, tx.value) const isSendTokenTx = await isTokenTransfer(tx.data, tx.value)
const customTx = tx.to !== safeAddress && !!tx.data && !isSendTokenTx
let executionTxHash let executionTxHash
const executionTx = confirmations.find((conf) => conf.type === TX_TYPE_EXECUTION) const executionTx = confirmations.find((conf) => conf.type === TX_TYPE_EXECUTION)
@ -70,11 +71,23 @@ export const buildTransactionFrom = async (
} }
let symbol = 'ETH' let symbol = 'ETH'
let decimals = 18
let decodedParams let decodedParams
if (isSendTokenTx) { if (isSendTokenTx) {
const tokenContract = await getHumanFriendlyToken() const tokenContract = await getHumanFriendlyToken()
const tokenInstance = await tokenContract.at(tx.to) const tokenInstance = await tokenContract.at(tx.to)
symbol = await tokenInstance.symbol() try {
[symbol, decimals] = await Promise.all([tokenInstance.symbol(), tokenInstance.decimals()])
} catch (err) {
const alternativeTokenInstance = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.to)
const [tokenSymbol, tokenDecimals] = await Promise.all([
alternativeTokenInstance.methods.symbol().call(),
alternativeTokenInstance.methods.decimals().call(),
])
symbol = web3.utils.toAscii(tokenSymbol)
decimals = tokenDecimals
}
const params = web3.eth.abi.decodeParameters(['address', 'uint256'], tx.data.slice(10)) const params = web3.eth.abi.decodeParameters(['address', 'uint256'], tx.data.slice(10))
decodedParams = { decodedParams = {
@ -93,6 +106,7 @@ export const buildTransactionFrom = async (
nonce: tx.nonce, nonce: tx.nonce,
value: tx.value.toString(), value: tx.value.toString(),
confirmations, confirmations,
decimals,
recipient: tx.to, recipient: tx.to,
data: tx.data ? tx.data : EMPTY_DATA, data: tx.data ? tx.data : EMPTY_DATA,
isExecuted: tx.isExecuted, isExecuted: tx.isExecuted,

View File

@ -11,7 +11,7 @@ const loadDefaultSafe = () => async (dispatch: ReduxDispatch<GlobalState>) => {
dispatch(setDefaultSafe(defaultSafe)) dispatch(setDefaultSafe(defaultSafe))
} catch (err) { } catch (err) {
// eslint-disable-next-line // eslint-disable-next-line
console.error('Error while getting defautl Safe from storage:', err) console.error('Error while getting default Safe from storage:', err)
} }
} }

View File

@ -1,11 +1,14 @@
// @flow // @flow
import type { Dispatch as ReduxDispatch, GetState } from 'redux' import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import { List } from 'immutable'
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
import { type Transaction } from '~/routes/safe/store/models/transaction' import { type Transaction } from '~/routes/safe/store/models/transaction'
import { userAccountSelector } from '~/logic/wallets/store/selectors' import { userAccountSelector } from '~/logic/wallets/store/selectors'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { type GlobalState } from '~/store' import { type GlobalState } from '~/store'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { import {
type NotifiedTransaction,
getApprovalTransaction, getApprovalTransaction,
getExecutionTransaction, getExecutionTransaction,
CALL, CALL,
@ -16,17 +19,17 @@ import {
import { import {
type Notification, type Notification,
type NotificationsQueue, type NotificationsQueue,
getNofiticationsFromTxType, getNotificationsFromTxType,
showSnackbar, showSnackbar,
} from '~/logic/notifications' } from '~/logic/notifications'
import { getErrorMessage } from '~/test/utils/ethereumErrors' import { getErrorMessage } from '~/test/utils/ethereumErrors'
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures // https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26 // https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
const generateSignaturesFromTxConfirmations = (tx: Transaction, preApprovingOwner?: string) => { export const generateSignaturesFromTxConfirmations = (confirmations: List<Confirmation>, preApprovingOwner?: string) => {
// The constant parts need to be sorted so that the recovered signers are sorted ascending // The constant parts need to be sorted so that the recovered signers are sorted ascending
// (natural order) by address (not checksummed). // (natural order) by address (not checksummed).
let confirmedAdresses = tx.confirmations.map((conf) => conf.owner.address) let confirmedAdresses = confirmations.map((conf) => conf.owner.address)
if (preApprovingOwner) { if (preApprovingOwner) {
confirmedAdresses = confirmedAdresses.push(preApprovingOwner) confirmedAdresses = confirmedAdresses.push(preApprovingOwner)
@ -59,7 +62,7 @@ const processTransaction = (
const threshold = (await safeInstance.getThreshold()).toNumber() const threshold = (await safeInstance.getThreshold()).toNumber()
const shouldExecute = threshold === tx.confirmations.size || approveAndExecute const shouldExecute = threshold === tx.confirmations.size || approveAndExecute
let sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress) let sigs = generateSignaturesFromTxConfirmations(tx.confirmations, approveAndExecute && userAddress)
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures // https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
if (!sigs) { if (!sigs) {
sigs = `0x000000000000000000000000${from.replace( sigs = `0x000000000000000000000000${from.replace(
@ -68,7 +71,7 @@ const processTransaction = (
)}000000000000000000000000000000000000000000000000000000000000000001` )}000000000000000000000000000000000000000000000000000000000000000001`
} }
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction) const notificationsQueue: NotificationsQueue = getNotificationsFromTxType(notifiedTransaction)
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar) const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
let pendingExecutionKey let pendingExecutionKey
@ -124,6 +127,7 @@ const processTransaction = (
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION, shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
) )
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar) showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
dispatch(fetchTransactions(safeAddress))
return receipt.transactionHash return receipt.transactionHash
}) })
@ -137,8 +141,6 @@ const processTransaction = (
console.error(`Error executing the TX: ${errMsg}`) console.error(`Error executing the TX: ${errMsg}`)
} }
dispatch(fetchTransactions(safeAddress))
return txHash return txHash
} }

View File

@ -10,7 +10,7 @@ export type SafeProps = {
address: string, address: string,
threshold: number, threshold: number,
owners: List<Owner>, owners: List<Owner>,
balances: Map<string, string>, balances?: Map<string, string>,
activeTokens: Set<string>, activeTokens: Set<string>,
ethBalance?: string, ethBalance?: string,
} }
@ -21,7 +21,7 @@ const SafeRecord: RecordFactory<SafeProps> = Record({
threshold: 0, threshold: 0,
ethBalance: 0, ethBalance: 0,
owners: List([]), owners: List([]),
activeTokens: new Set([]), activeTokens: new Set(),
balances: Map({}), balances: Map({}),
}) })

View File

@ -21,6 +21,7 @@ export type TransactionProps = {
customTx: boolean, customTx: boolean,
safeTxHash: string, safeTxHash: string,
executionTxHash?: string, executionTxHash?: string,
decimals?: number,
cancelled?: boolean, cancelled?: boolean,
status?: TransactionStatus, status?: TransactionStatus,
isTokenTransfer: boolean, isTokenTransfer: boolean,
@ -45,6 +46,7 @@ export const makeTransaction: RecordFactory<TransactionProps> = Record({
cancellationTx: false, cancellationTx: false,
customTx: false, customTx: false,
status: 'awaiting', status: 'awaiting',
decimals: 18,
isTokenTransfer: false, isTokenTransfer: false,
decodedParams: {}, decodedParams: {},
}) })

View File

@ -15,7 +15,7 @@ import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/lo
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens' import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
import notifications, { import notifications, {
NOTIFICATIONS_REDUCER_ID, NOTIFICATIONS_REDUCER_ID,
type State as NotificationsState, type NotificationReducerState as NotificationsState,
} from '~/logic/notifications/store/reducer/notifications' } from '~/logic/notifications/store/reducer/notifications'
export const history = createBrowserHistory() export const history = createBrowserHistory()

View File

@ -14,7 +14,6 @@ import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/Transactions/T
import { useTestAccountAt, resetTestAccount } from './utils/accounts' import { useTestAccountAt, resetTestAccount } from './utils/accounts'
import { import {
CONFIRM_TX_BTN_TEST_ID, CONFIRM_TX_BTN_TEST_ID,
EXECUTE_TX_BTN_TEST_ID,
} from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow' } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal' import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal'
@ -67,13 +66,7 @@ describe('DOM > Feature > Sending Funds', () => {
const approveTxBtn = await waitForElement(() => SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID)) const approveTxBtn = await waitForElement(() => SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
fireEvent.click(approveTxBtn) fireEvent.click(approveTxBtn)
// EXECUTE TX await sleep(1000)
const executeTxBtn = await waitForElement(() => SafeDom.getByTestId(EXECUTE_TX_BTN_TEST_ID))
fireEvent.click(executeTxBtn)
const confirmReviewTxBtn = await waitForElement(() => SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
fireEvent.click(confirmReviewTxBtn)
await sleep(500)
// THEN // THEN
const safeFunds = await getBalanceInEtherOf(safeAddress) const safeFunds = await getBalanceInEtherOf(safeAddress)

581
yarn.lock
View File

@ -2,10 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/cli@7.6.2": "@babel/cli@7.6.4":
version "7.6.2" version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.2.tgz#4ce8b5b4b2e4b4c1b7bd841cec62085e2dfc4465" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.4.tgz#9b35a4e15fa7d8f487418aaa8229c8b0bc815f20"
integrity sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ== integrity sha512-tqrDyvPryBM6xjIyKKUwr3s8CzmmYidwgdswd7Uc/Cv0ogZcuS1TYQTLx/eWKP3UbJ6JxZAiYlBZabXm/rtRsQ==
dependencies: dependencies:
commander "^2.8.1" commander "^2.8.1"
convert-source-map "^1.1.0" convert-source-map "^1.1.0"
@ -53,18 +53,18 @@
semver "^5.4.1" semver "^5.4.1"
source-map "^0.5.0" source-map "^0.5.0"
"@babel/core@7.6.2": "@babel/core@7.6.4":
version "7.6.2" version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ== integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
dependencies: dependencies:
"@babel/code-frame" "^7.5.5" "@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.2" "@babel/generator" "^7.6.4"
"@babel/helpers" "^7.6.2" "@babel/helpers" "^7.6.2"
"@babel/parser" "^7.6.2" "@babel/parser" "^7.6.4"
"@babel/template" "^7.6.0" "@babel/template" "^7.6.0"
"@babel/traverse" "^7.6.2" "@babel/traverse" "^7.6.3"
"@babel/types" "^7.6.0" "@babel/types" "^7.6.3"
convert-source-map "^1.1.0" convert-source-map "^1.1.0"
debug "^4.1.0" debug "^4.1.0"
json5 "^2.1.0" json5 "^2.1.0"
@ -114,6 +114,26 @@
lodash "^4.17.13" lodash "^4.17.13"
source-map "^0.5.0" source-map "^0.5.0"
"@babel/generator@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.3.tgz#71d5375264f93ec7bac7d9f35a67067733f5578e"
integrity sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==
dependencies:
"@babel/types" "^7.6.3"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.6.1"
"@babel/generator@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
dependencies:
"@babel/types" "^7.6.3"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.5.0"
"@babel/helper-annotate-as-pure@^7.0.0": "@babel/helper-annotate-as-pure@^7.0.0":
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
@ -343,6 +363,16 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1"
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg== integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==
"@babel/parser@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.3.tgz#9eff8b9c3eeae16a74d8d4ff30da2bd0d6f0487e"
integrity sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==
"@babel/parser@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
"@babel/plugin-proposal-async-generator-functions@^7.2.0": "@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e"
@ -719,10 +749,10 @@
"@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13" lodash "^4.17.13"
"@babel/plugin-transform-block-scoping@^7.6.2": "@babel/plugin-transform-block-scoping@^7.6.3":
version "7.6.2" version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz#96c33ab97a9ae500cc6f5b19e04a7e6553360a79" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a"
integrity sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ== integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13" lodash "^4.17.13"
@ -914,10 +944,10 @@
dependencies: dependencies:
regexp-tree "^0.1.6" regexp-tree "^0.1.6"
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.2": "@babel/plugin-transform-named-capturing-groups-regex@^7.6.3":
version "7.6.2" version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz#c1ca0bb84b94f385ca302c3932e870b0fb0e522b" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz#aaa6e409dd4fb2e50b6e2a91f7e3a3149dbce0cf"
integrity sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g== integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==
dependencies: dependencies:
regexpu-core "^4.6.0" regexpu-core "^4.6.0"
@ -1157,10 +1187,10 @@
js-levenshtein "^1.1.3" js-levenshtein "^1.1.3"
semver "^5.5.0" semver "^5.5.0"
"@babel/preset-env@7.6.2": "@babel/preset-env@7.6.3":
version "7.6.2" version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271"
integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==
dependencies: dependencies:
"@babel/helper-module-imports" "^7.0.0" "@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0"
@ -1178,7 +1208,7 @@
"@babel/plugin-transform-arrow-functions" "^7.2.0" "@babel/plugin-transform-arrow-functions" "^7.2.0"
"@babel/plugin-transform-async-to-generator" "^7.5.0" "@babel/plugin-transform-async-to-generator" "^7.5.0"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0"
"@babel/plugin-transform-block-scoping" "^7.6.2" "@babel/plugin-transform-block-scoping" "^7.6.3"
"@babel/plugin-transform-classes" "^7.5.5" "@babel/plugin-transform-classes" "^7.5.5"
"@babel/plugin-transform-computed-properties" "^7.2.0" "@babel/plugin-transform-computed-properties" "^7.2.0"
"@babel/plugin-transform-destructuring" "^7.6.0" "@babel/plugin-transform-destructuring" "^7.6.0"
@ -1193,7 +1223,7 @@
"@babel/plugin-transform-modules-commonjs" "^7.6.0" "@babel/plugin-transform-modules-commonjs" "^7.6.0"
"@babel/plugin-transform-modules-systemjs" "^7.5.0" "@babel/plugin-transform-modules-systemjs" "^7.5.0"
"@babel/plugin-transform-modules-umd" "^7.2.0" "@babel/plugin-transform-modules-umd" "^7.2.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.3"
"@babel/plugin-transform-new-target" "^7.4.4" "@babel/plugin-transform-new-target" "^7.4.4"
"@babel/plugin-transform-object-super" "^7.5.5" "@babel/plugin-transform-object-super" "^7.5.5"
"@babel/plugin-transform-parameters" "^7.4.4" "@babel/plugin-transform-parameters" "^7.4.4"
@ -1206,7 +1236,7 @@
"@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-template-literals" "^7.4.4"
"@babel/plugin-transform-typeof-symbol" "^7.2.0" "@babel/plugin-transform-typeof-symbol" "^7.2.0"
"@babel/plugin-transform-unicode-regex" "^7.6.2" "@babel/plugin-transform-unicode-regex" "^7.6.2"
"@babel/types" "^7.6.0" "@babel/types" "^7.6.3"
browserslist "^4.6.0" browserslist "^4.6.0"
core-js-compat "^3.1.1" core-js-compat "^3.1.1"
invariant "^2.2.2" invariant "^2.2.2"
@ -1288,6 +1318,17 @@
"@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/preset-react@7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.6.3.tgz#d5242c828322520205ae4eda5d4f4f618964e2f6"
integrity sha512-07yQhmkZmRAfwREYIQgW0HEwMY9GBJVuPY4Q12UC72AbfaawuupVWa8zQs2tlL+yun45Nv/1KreII/0PLfEsgA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-react-display-name" "^7.0.0"
"@babel/plugin-transform-react-jsx" "^7.0.0"
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/preset-typescript@7.3.3": "@babel/preset-typescript@7.3.3":
version "7.3.3" version "7.3.3"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a"
@ -1387,6 +1428,21 @@
globals "^11.1.0" globals "^11.1.0"
lodash "^4.17.13" lodash "^4.17.13"
"@babel/traverse@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.3"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/parser" "^7.6.3"
"@babel/types" "^7.6.3"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5": "@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5":
version "7.5.5" version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
@ -1405,6 +1461,15 @@
lodash "^4.17.13" lodash "^4.17.13"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@cnakazawa/watch@^1.0.3": "@cnakazawa/watch@^1.0.3":
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@ -1760,10 +1825,10 @@
"@types/istanbul-reports" "^1.1.1" "@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0" "@types/yargs" "^13.0.0"
"@material-ui/core@4.5.0": "@material-ui/core@4.5.1":
version "4.5.0" version "4.5.1"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.5.0.tgz#7e57cc40988c71b6340e3b2569b47dbac1820351" resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.5.1.tgz#6f1bbb298cc2893b29169ab9398a00ca5a6ac971"
integrity sha512-UHVAjU+1uDtA+OMBNBHb4RlCZOu514XeYPafNJv+GTdXBDr1SCPK7yqRE6TV1/bulxlDusTgu5Q6BAUgpmO4MA== integrity sha512-6pyk7diT7bflf4qUpqgPCpKYqjhRHPFwsgEV2Gv71lMqwxuRygFGHE2TdZ+l5T249H66Doj2P/j6fW7yzgxTWw==
dependencies: dependencies:
"@babel/runtime" "^7.4.4" "@babel/runtime" "^7.4.4"
"@material-ui/styles" "^4.5.0" "@material-ui/styles" "^4.5.0"
@ -1781,10 +1846,10 @@
prop-types "^15.7.2" prop-types "^15.7.2"
react-transition-group "^4.3.0" react-transition-group "^4.3.0"
"@material-ui/icons@4.4.3": "@material-ui/icons@4.5.1":
version "4.4.3" version "4.5.1"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.4.3.tgz#5d4346ddbb2673a1b57ebc78fd6d50bcd88711db" resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.5.1.tgz#6963bad139e938702ece85ca43067688018f04f8"
integrity sha512-HVVvUyc/78kmaBd93LkfWyGkXMM+zOMKzUfulWXxaV/fFAZ3N0pD0oHjWUd94zrOoF3tZP9JC7EPlIpIcZSNow== integrity sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==
dependencies: dependencies:
"@babel/runtime" "^7.4.4" "@babel/runtime" "^7.4.4"
@ -1930,17 +1995,17 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@storybook/addon-actions@5.2.1": "@storybook/addon-actions@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.1.tgz#2096e7f938b289be48af6f0adfd620997e7a420c" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.4.tgz#79d13c1b6e58c75dd548eb162aa6626a93dfd041"
integrity sha512-tu4LGeRGAq+sLlsRPE1PzGyYU9JyM3HMLXnOCh5dvRSS8wnoDw1zQ55LPOXH6aoJGdsrvktiw+uTVf4OyN7ryg== integrity sha512-5E8uXopy6Gq5R3MXrPf0VM9QiLaGLxLCXtDYHQ0gku+HhPYR25KQudS/PyuO+OWzuyB0fsvTi240B3zw+zilOg==
dependencies: dependencies:
"@storybook/addons" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/api" "5.2.1" "@storybook/api" "5.2.4"
"@storybook/client-api" "5.2.1" "@storybook/client-api" "5.2.4"
"@storybook/components" "5.2.1" "@storybook/components" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/theming" "5.2.1" "@storybook/theming" "5.2.4"
core-js "^3.0.1" core-js "^3.0.1"
fast-deep-equal "^2.0.1" fast-deep-equal "^2.0.1"
global "^4.3.2" global "^4.3.2"
@ -1966,17 +2031,18 @@
react-inspector "^2.2.2" react-inspector "^2.2.2"
uuid "^3.2.1" uuid "^3.2.1"
"@storybook/addon-knobs@5.2.1": "@storybook/addon-knobs@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.1.tgz#6bc2f7e254ccce09d6f5136e9cce63cd808c9853" resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.4.tgz#f5db98235bc2870361dd3c224f8c10f901592c43"
integrity sha512-JCSqrGYyVVBNkudhvla7qc9m0/Mn1UMaMzIxH5kewEE1KWZcCkdXD5hDASN39pkn3mX1yyqveP8jiyIL9vVBLg== integrity sha512-VYxbDARJs5RwTEOlcfa98tkDXLcRocB7QXLqt8wwCdXPIqkuoVeQLROXGYJm2NzSn49RyHPKUuVWnRhy34qBbQ==
dependencies: dependencies:
"@storybook/addons" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/api" "5.2.1" "@storybook/api" "5.2.4"
"@storybook/client-api" "5.2.1" "@storybook/client-api" "5.2.4"
"@storybook/components" "5.2.1" "@storybook/components" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/theming" "5.2.1" "@storybook/theming" "5.2.4"
"@types/react-color" "^3.0.1"
copy-to-clipboard "^3.0.8" copy-to-clipboard "^3.0.8"
core-js "^3.0.1" core-js "^3.0.1"
escape-html "^1.0.3" escape-html "^1.0.3"
@ -1989,29 +2055,29 @@
react-lifecycles-compat "^3.0.4" react-lifecycles-compat "^3.0.4"
react-select "^3.0.0" react-select "^3.0.0"
"@storybook/addon-links@5.2.1": "@storybook/addon-links@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.1.tgz#ec1fc92ed4d840ba758f40167c752f48562a906f" resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.4.tgz#5a5d631dbd66bf5800d4be7660b79568085a210d"
integrity sha512-N5f+lzai+ctHfzHoYWECYsg3lKGJuqhkVctro46fHSW7s/GB8+l78nDcV7hDjNEXDES8QN5C1fPYihatdgpSJA== integrity sha512-MG+Qne4gUWGYx2qQuLQXNcl7oOBF4PbIcR0oboZNrkZ+D+6f3nHwyb53CTtzVTc+SF45CFFYLHvFdGZvv5fcAw==
dependencies: dependencies:
"@storybook/addons" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.1" "@storybook/router" "5.2.4"
common-tags "^1.8.0" common-tags "^1.8.0"
core-js "^3.0.1" core-js "^3.0.1"
global "^4.3.2" global "^4.3.2"
prop-types "^15.7.2" prop-types "^15.7.2"
qs "^6.6.0" qs "^6.6.0"
"@storybook/addons@5.2.1": "@storybook/addons@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.1.tgz#6e52aa1fa2737e170fb675eb1fcceebd0a915a0b" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.4.tgz#5c4f031e403c90a517cd6d208ec51d7e2455683a"
integrity sha512-kdx97tTKsMf/lBlT40uLYsHMF1J71mn2j41RNaCXmWw/PrKCDmiNfinemN2wtbwRSvGqb3q/BAqjKLvUtWynGg== integrity sha512-Q+bnVlBA308qnELxnh18hBDRSUgltR9KbV537285dUL/okv/NC6n51mxJwIaG+ksBW2wU+5e6tqSayaKF3uHLw==
dependencies: dependencies:
"@storybook/api" "5.2.1" "@storybook/api" "5.2.4"
"@storybook/channels" "5.2.1" "@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
core-js "^3.0.1" core-js "^3.0.1"
global "^4.3.2" global "^4.3.2"
util-deprecate "^1.0.2" util-deprecate "^1.0.2"
@ -2051,16 +2117,16 @@
telejson "^2.2.1" telejson "^2.2.1"
util-deprecate "^1.0.2" util-deprecate "^1.0.2"
"@storybook/api@5.2.1": "@storybook/api@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.1.tgz#b9cd6639019e044a8ade6fb358cade79c0e3b5d3" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.4.tgz#b0b3dbd93444d163a80b455fb877d816a37b3149"
integrity sha512-EXN6sqkGHRuNq0W6BZXOlxe2I2dmN0yUdQLiUOpzH2I3mXnVHpad/0v76dRc9fZbC4LaYUSxR8lBTr0rqIb4mA== integrity sha512-KqAB+NkHIHdwu749NDP+7i44jy1bFgpq7GTJlG+sx/XLZHQveK/8yn109g9bXHFth7SvdXI1+9GA/apzwBU/Mw==
dependencies: dependencies:
"@storybook/channels" "5.2.1" "@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.1" "@storybook/router" "5.2.4"
"@storybook/theming" "5.2.1" "@storybook/theming" "5.2.4"
core-js "^3.0.1" core-js "^3.0.1"
fast-deep-equal "^2.0.1" fast-deep-equal "^2.0.1"
global "^4.3.2" global "^4.3.2"
@ -2071,19 +2137,19 @@
semver "^6.0.0" semver "^6.0.0"
shallow-equal "^1.1.0" shallow-equal "^1.1.0"
store2 "^2.7.1" store2 "^2.7.1"
telejson "^2.2.2" telejson "^3.0.2"
util-deprecate "^1.0.2" util-deprecate "^1.0.2"
"@storybook/channel-postmessage@5.2.1": "@storybook/channel-postmessage@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.1.tgz#85541f926d61eedbe2a687bb394d37fc06252751" resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.4.tgz#e3735bdce42156e54bf462083aebaf23245ab5c3"
integrity sha512-gmnn9qU1iLCpfF6bZuEM3QQOZsAviWeIpiezjrd/qkxatgr3qtbXd4EoZpcVuQw314etarWtNxVpcX6PXcASjQ== integrity sha512-ic7/Ho8z2/aOMjoEbr5p8rijOfO3SZdJnwMvDdUxrqvYq7yACZWidPo3w2+iBwQi9HLqEsWesP1c2doJBxVGRw==
dependencies: dependencies:
"@storybook/channels" "5.2.1" "@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
core-js "^3.0.1" core-js "^3.0.1"
global "^4.3.2" global "^4.3.2"
telejson "^2.2.2" telejson "^3.0.2"
"@storybook/channels@5.1.9": "@storybook/channels@5.1.9":
version "5.1.9" version "5.1.9"
@ -2092,24 +2158,24 @@
dependencies: dependencies:
core-js "^3.0.1" core-js "^3.0.1"
"@storybook/channels@5.2.1": "@storybook/channels@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.1.tgz#e5e35f6d9fb1b1fba4f18b171f31d5f6540f3bef" resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.4.tgz#7ab5c9478517ddb9e8ac0c4188b408b9d7e6b7e4"
integrity sha512-AsF/Hwx91SDOgiOGOBSWS8EJAgqVm939n2nkfdLSJQQmX5EdPRAc3EIE3f13tyQub2yNx0OR4UzQDWgjwfVsEQ== integrity sha512-/r39yEZ5QiGdiq95DhXBypdBo7urkD3Sp1WDyK48uGkZ0gdHWSPy3BBy8OJhEhfNz7nVisTiVIBr4gIrubKDjw==
dependencies: dependencies:
core-js "^3.0.1" core-js "^3.0.1"
"@storybook/client-api@5.2.1": "@storybook/client-api@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.1.tgz#bdd335187279a4ab45e20d6d5e9131e5f7098acf" resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.4.tgz#2507a3a739a6f6b13e4afefb938a6f3eab34ed55"
integrity sha512-VxexqxrbORCGqwx2j0/91Eu1A/vq+rSVIesWwzIowmoLfBwRwDdskO20Yn9U7iMSpux4RvHGF6y1Q1ZtnXm9aA== integrity sha512-SOwzEFHoNapURhNqdcI7HA76o5tkWvs2+2s++i/S7xsAd3KyefIVDOdqSMlAxJkxZb8Mlrb3UNRxlrpA8SZqNA==
dependencies: dependencies:
"@storybook/addons" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/channel-postmessage" "5.2.1" "@storybook/channel-postmessage" "5.2.4"
"@storybook/channels" "5.2.1" "@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.1" "@storybook/router" "5.2.4"
common-tags "^1.8.0" common-tags "^1.8.0"
core-js "^3.0.1" core-js "^3.0.1"
eventemitter3 "^4.0.0" eventemitter3 "^4.0.0"
@ -2127,10 +2193,10 @@
dependencies: dependencies:
core-js "^3.0.1" core-js "^3.0.1"
"@storybook/client-logger@5.2.1": "@storybook/client-logger@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.1.tgz#5c1f122b65386f04a6ad648808dfa89f2d852d7a" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.4.tgz#6ebe37cbc92e0efe27c7119f52d94f647fbb470c"
integrity sha512-wzxSE9t3DaLCdd/gnGFnjevmYRZ92F3TEwhUP/QDXM9cZkNsRKHkjE61qjiO5aQPaZQG6Ea9ayWEQEMgZXDucg== integrity sha512-ofp6QQPQZBU+RvlAH5KpZRsfAFHecCZDnl/7YG6FwjHseJr3jHTYmBGGjJDMHFHq+Q7FGQu/yVb9lMFgoQ43QQ==
dependencies: dependencies:
core-js "^3.0.1" core-js "^3.0.1"
@ -2143,13 +2209,13 @@
glamorous "^4.12.1" glamorous "^4.12.1"
prop-types "^15.6.1" prop-types "^15.6.1"
"@storybook/components@5.2.1": "@storybook/components@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.1.tgz#a4519c5d435c2c25c481e2b64a768e1e568a223f" resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.4.tgz#9ecd080416eac4e8453030dd601cbeaa32ce4126"
integrity sha512-cik5J/mTm1b1TOI17qM+2Mikk3rjb3SbBD4WlNz3Zvn+Hw0ukgbx6kQwVBgujhMlDtsHreidyEgIg4TM13S0Tg== integrity sha512-APhw+XGag0RTCRJ8eCWKVr8dLt9SRqnS8LtzcZJbokCYRxRTFzhmX2eVEE1v+d0gHib1/yh2COxOjMzv3m/rQA==
dependencies: dependencies:
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
"@storybook/theming" "5.2.1" "@storybook/theming" "5.2.4"
"@types/react-syntax-highlighter" "10.1.0" "@types/react-syntax-highlighter" "10.1.0"
core-js "^3.0.1" core-js "^3.0.1"
global "^4.3.2" global "^4.3.2"
@ -2174,32 +2240,32 @@
dependencies: dependencies:
core-js "^3.0.1" core-js "^3.0.1"
"@storybook/core-events@5.2.1": "@storybook/core-events@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.1.tgz#bc28d704938d26dd544d0362d38ef08e8cfed916" resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.4.tgz#210c968e96e2fc031cad1d3a09b06fa69ae433fc"
integrity sha512-AIYV/I+baQ0KxvEM7QAKqUedLn2os0XU9HTdtfZJTC3U9wjmR2ah2ScD6T0n7PBz3MderkvZG6dNjs9h8gRquQ== integrity sha512-nQknCmaz2S2HW6PSGcuFzve7Y1Js2Cb268vUG0ZMNtJZwFawqYc+KSQHqmOY0pVm8dyROTcWCudPA0k+hk6N5Q==
dependencies: dependencies:
core-js "^3.0.1" core-js "^3.0.1"
"@storybook/core@5.2.1": "@storybook/core@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.1.tgz#3aa17c6fa9b02704723501d32884453869e3c06c" resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.4.tgz#188509ac0eb0c85144816c385c21acfb5c8fbefd"
integrity sha512-mGGvN3GWeLxZ9lYZ4IuD1IoJD+cn6XXm2Arzw+k6KEtJJDFrC5SjESTDGLVFienX5s2tgH4FjYb9Ps9sKfhHlg== integrity sha512-r5kDgZETNawHxpsAPw+h+pRk6l/mJhsSHeDo9/OdYtYFW7lmk2gadViXOTM+6gIWc6vQ8y750bgkahmyIIY0nQ==
dependencies: dependencies:
"@babel/plugin-proposal-class-properties" "^7.3.3" "@babel/plugin-proposal-class-properties" "^7.3.3"
"@babel/plugin-proposal-object-rest-spread" "^7.3.2" "@babel/plugin-proposal-object-rest-spread" "^7.3.2"
"@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-syntax-dynamic-import" "^7.2.0"
"@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/plugin-transform-react-constant-elements" "^7.2.0"
"@babel/preset-env" "^7.4.5" "@babel/preset-env" "^7.4.5"
"@storybook/addons" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/channel-postmessage" "5.2.1" "@storybook/channel-postmessage" "5.2.4"
"@storybook/client-api" "5.2.1" "@storybook/client-api" "5.2.4"
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/node-logger" "5.2.1" "@storybook/node-logger" "5.2.4"
"@storybook/router" "5.2.1" "@storybook/router" "5.2.4"
"@storybook/theming" "5.2.1" "@storybook/theming" "5.2.4"
"@storybook/ui" "5.2.1" "@storybook/ui" "5.2.4"
airbnb-js-shims "^1 || ^2" airbnb-js-shims "^1 || ^2"
ansi-to-html "^0.6.11" ansi-to-html "^0.6.11"
autoprefixer "^9.4.9" autoprefixer "^9.4.9"
@ -2255,10 +2321,10 @@
webpack-dev-middleware "^3.7.0" webpack-dev-middleware "^3.7.0"
webpack-hot-middleware "^2.25.0" webpack-hot-middleware "^2.25.0"
"@storybook/node-logger@5.2.1": "@storybook/node-logger@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.1.tgz#00d8c0dc9dfd482e7d1d244a59c46726c6b761d9" resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.4.tgz#52dc380c76bdac68de35e7d72efe920c3ba52a23"
integrity sha512-rz+snXZyKwTegKEf15w4uaFWIKpgaWzTw+Ar8mxa+mX7C2DP65TOc+JGYZ7lsXdred+0WP0DhnmhGu2cX8z3lA== integrity sha512-4OOzce02IAfrRv+Y7h3icyw6WIuDekpWF2eYjgYVVvAJYklCEwgeBTBCY0/2TJjPPTBDPUKHVP1Bdz3Vpci9pA==
dependencies: dependencies:
chalk "^2.4.2" chalk "^2.4.2"
core-js "^3.0.1" core-js "^3.0.1"
@ -2266,18 +2332,19 @@
pretty-hrtime "^1.0.3" pretty-hrtime "^1.0.3"
regenerator-runtime "^0.12.1" regenerator-runtime "^0.12.1"
"@storybook/react@5.2.1": "@storybook/react@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.1.tgz#860970fa8f0d49967862b496af4ef3712f0b96dd" resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.4.tgz#305db3fa7cd113c185aba97e5239d7754dc14859"
integrity sha512-brUG8iK2+1Fk5VFZWpAoSokCx21MaPX1zSAVA+Z/Ia0I0sFfurhpQgAGlVePTy9r7dtEEEdniZVtJOH/tHqk4Q== integrity sha512-AO0qwbD/2UGe5CrVizbaek+gCAPWkPVc0KUk38cT1mcuLpXwt1zZe7iHLQf2zOeBVSiBkPLOHrEtzDfnIJXKFQ==
dependencies: dependencies:
"@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/plugin-transform-react-constant-elements" "^7.2.0"
"@babel/preset-flow" "^7.0.0" "@babel/preset-flow" "^7.0.0"
"@babel/preset-react" "^7.0.0" "@babel/preset-react" "^7.0.0"
"@storybook/addons" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/core" "5.2.1" "@storybook/core" "5.2.4"
"@storybook/node-logger" "5.2.1" "@storybook/node-logger" "5.2.4"
"@svgr/webpack" "^4.0.3" "@svgr/webpack" "^4.0.3"
"@types/webpack-env" "^1.13.7"
babel-plugin-add-react-displayname "^0.0.5" babel-plugin-add-react-displayname "^0.0.5"
babel-plugin-named-asset-import "^0.3.1" babel-plugin-named-asset-import "^0.3.1"
babel-plugin-react-docgen "^3.0.0" babel-plugin-react-docgen "^3.0.0"
@ -2304,10 +2371,10 @@
memoizerific "^1.11.3" memoizerific "^1.11.3"
qs "^6.6.0" qs "^6.6.0"
"@storybook/router@5.2.1": "@storybook/router@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.1.tgz#9c49df79343d3be10c7f984858fb5c9ae3eb7491" resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.4.tgz#e47af60478da7730c7d82de3d089d12c17077333"
integrity sha512-Mlk275cyPoKtnP4DwQ5D8gTfnaRPL6kDZOSn0wbTMa6pQOfYKgJsa7tjzeAtZuZ/j8hKI4gAfT/auMgH6g+94A== integrity sha512-GL7eGdj5oYST0mE9fThJB9ye9tTTgrP+aP3okZ6MeMGtNytb7bmJRpAD2E4ouuPTQVppyHI5re8g/HUxUNOT1g==
dependencies: dependencies:
"@reach/router" "^1.2.1" "@reach/router" "^1.2.1"
"@types/reach__router" "^1.2.3" "@types/reach__router" "^1.2.3"
@ -2335,14 +2402,14 @@
prop-types "^15.7.2" prop-types "^15.7.2"
resolve-from "^5.0.0" resolve-from "^5.0.0"
"@storybook/theming@5.2.1": "@storybook/theming@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.1.tgz#913e383632e4702035a107c2cc5e5cb27231b389" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.4.tgz#79c99f65992082fdb4d39fb652db435dd6bebaca"
integrity sha512-lbAfcyI7Tx8swduIPmlu/jdWzqTBN/v82IEQbZbPR4LS5OHRPmhXPNgFGrcH4kFAiD0GoezSsdum1x0ZZpsQUQ== integrity sha512-2ZlqBrmnm8N0352Fnu2+GB3pEsHL4Eb2eKxV0VLLgkjJuAlm7CK6+I/e4ZknQWxwYm2pQj1y6ta68A62fGBYyA==
dependencies: dependencies:
"@emotion/core" "^10.0.14" "@emotion/core" "^10.0.14"
"@emotion/styled" "^10.0.14" "@emotion/styled" "^10.0.14"
"@storybook/client-logger" "5.2.1" "@storybook/client-logger" "5.2.4"
common-tags "^1.8.0" common-tags "^1.8.0"
core-js "^3.0.1" core-js "^3.0.1"
deep-object-diff "^1.1.0" deep-object-diff "^1.1.0"
@ -2353,21 +2420,19 @@
prop-types "^15.7.2" prop-types "^15.7.2"
resolve-from "^5.0.0" resolve-from "^5.0.0"
"@storybook/ui@5.2.1": "@storybook/ui@5.2.4":
version "5.2.1" version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.1.tgz#ceba1657a232efd10f839027f8ae274e370c89f6" resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.4.tgz#7116c9be0dcdcc5d5b425d998263de1e27cdc078"
integrity sha512-h6Yf1ro/nZcz4nQAU+eSVPxVmpqv7uT7RMb3Vz+VLTY59IEA/sWcoIgA4MIxwf14nVcWOqSmVBJzNKWwc+NGJw== integrity sha512-zsS43k1h4bWEW6oj9FNHlUL3niHoJJ8v7iqYbRtVM12rxrYhV3K8TGVG3LCuNB75i3Be0Myy+/RHA4x9kco08A==
dependencies: dependencies:
"@storybook/addon-actions" "5.2.1" "@storybook/addons" "5.2.4"
"@storybook/addon-knobs" "5.2.1" "@storybook/api" "5.2.4"
"@storybook/addons" "5.2.1" "@storybook/channels" "5.2.4"
"@storybook/api" "5.2.1" "@storybook/client-logger" "5.2.4"
"@storybook/channels" "5.2.1" "@storybook/components" "5.2.4"
"@storybook/client-logger" "5.2.1" "@storybook/core-events" "5.2.4"
"@storybook/components" "5.2.1" "@storybook/router" "5.2.4"
"@storybook/core-events" "5.2.1" "@storybook/theming" "5.2.4"
"@storybook/router" "5.2.1"
"@storybook/theming" "5.2.1"
copy-to-clipboard "^3.0.8" copy-to-clipboard "^3.0.8"
core-js "^3.0.1" core-js "^3.0.1"
core-js-pure "^3.0.1" core-js-pure "^3.0.1"
@ -2383,7 +2448,7 @@
qs "^6.6.0" qs "^6.6.0"
react "^16.8.3" react "^16.8.3"
react-dom "^16.8.3" react-dom "^16.8.3"
react-draggable "^3.3.2" react-draggable "^4.0.3"
react-helmet-async "^1.0.2" react-helmet-async "^1.0.2"
react-hotkeys "2.0.0-pre4" react-hotkeys "2.0.0-pre4"
react-sizeme "^2.6.7" react-sizeme "^2.6.7"
@ -2391,7 +2456,7 @@
resolve-from "^5.0.0" resolve-from "^5.0.0"
semver "^6.0.0" semver "^6.0.0"
store2 "^2.7.1" store2 "^2.7.1"
telejson "^2.2.2" telejson "^3.0.2"
util-deprecate "^1.0.2" util-deprecate "^1.0.2"
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0": "@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
@ -2516,10 +2581,10 @@
pretty-format "^24.8.0" pretty-format "^24.8.0"
wait-for-expect "^1.3.0" wait-for-expect "^1.3.0"
"@testing-library/jest-dom@4.1.0": "@testing-library/jest-dom@4.1.2":
version "4.1.0" version "4.1.2"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.0.tgz#69d372e54e4e33be3fd34f3848ec0e8e9d099276" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.2.tgz#e523047191379abd67cf0896dfd93cabc7e33eab"
integrity sha512-cKAONDmJKGJ2DSu6R/+lgA8i8uyZIx4CaOiiK0yMjp+2UecH6kfjunJiy5hfExKMtR74eyzFriqO1w9aTC8VyQ== integrity sha512-fNf2rCfu0dBD4DmpzqR2ibsaFlFojrWI/EuU8LLqv73CzFIMvT2RMq88p5JVRe4DfeNj0mu0MQ5FTG4mQ0qFaA==
dependencies: dependencies:
"@babel/runtime" "^7.5.1" "@babel/runtime" "^7.5.1"
chalk "^2.4.1" chalk "^2.4.1"
@ -2628,6 +2693,11 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a"
integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw== integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==
"@types/is-function@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.0.tgz#1b0b819b1636c7baf0d6785d030d12edf70c3e83"
integrity sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -2691,6 +2761,13 @@
"@types/history" "*" "@types/history" "*"
"@types/react" "*" "@types/react" "*"
"@types/react-color@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.1.tgz#5433e2f503ea0e0831cbc6fd0c20f8157d93add0"
integrity sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==
dependencies:
"@types/react" "*"
"@types/react-dom@*": "@types/react-dom@*":
version "16.9.0" version "16.9.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.0.tgz#ba6ddb00bf5de700b0eb91daa452081ffccbfdea" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.0.tgz#ba6ddb00bf5de700b0eb91daa452081ffccbfdea"
@ -2752,6 +2829,11 @@
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf" resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf"
integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw== integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==
"@types/webpack-env@^1.13.7":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.14.0.tgz#8edfc5f8e6eae20eeed3ca0d02974ed4ee5e4efc"
integrity sha512-Fv+0gYJzE/czLoRKq+gnXWr4yBpPM3tO3C8pDLFwqVKlMICQUq5OsxwwFZYDaVr7+L6mgNDp16iOcJHEz3J5RQ==
"@types/yargs-parser@*": "@types/yargs-parser@*":
version "13.0.0" version "13.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0"
@ -2942,10 +3024,10 @@
"@webassemblyjs/wast-parser" "1.8.5" "@webassemblyjs/wast-parser" "1.8.5"
"@xtuc/long" "4.2.2" "@xtuc/long" "4.2.2"
"@welldone-software/why-did-you-render@3.3.5": "@welldone-software/why-did-you-render@3.3.8":
version "3.3.5" version "3.3.8"
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-3.3.5.tgz#ba301216ebd7b0283e85995357706223186e6afb" resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-3.3.8.tgz#a8b69e6d64e36bab676c03ca4630032bdf6ea5b1"
integrity sha512-hn3U5eU7tVEk3A14tRuYHolbBQv6IRmVd/ukNDeyKmC1IbxBck8j8HnFji4ARvw7Z2kJhUwKtXpRI2+gxVolBA== integrity sha512-DtmXat8vPJuQlSG9BD0dW9baViZUzFtHMMTHAGnQpRosq7U0kOkEoPXoLY/mRKEfI3SJkxX9R/dwIVh2CQ+rKw==
dependencies: dependencies:
lodash "^4" lodash "^4"
@ -3600,7 +3682,20 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@9.6.1, autoprefixer@^9.4.9: autoprefixer@9.6.5:
version "9.6.5"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.5.tgz#98f4afe7e93cccf323287515d426019619775e5e"
integrity sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==
dependencies:
browserslist "^4.7.0"
caniuse-lite "^1.0.30000999"
chalk "^2.4.2"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
postcss "^7.0.18"
postcss-value-parser "^4.0.2"
autoprefixer@^9.4.9:
version "9.6.1" version "9.6.1"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw== integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==
@ -5013,6 +5108,15 @@ browserslist@^4.0.0, browserslist@^4.5.2, browserslist@^4.6.0, browserslist@^4.6
electron-to-chromium "^1.3.191" electron-to-chromium "^1.3.191"
node-releases "^1.1.25" node-releases "^1.1.25"
browserslist@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17"
integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==
dependencies:
caniuse-lite "^1.0.30000989"
electron-to-chromium "^1.3.247"
node-releases "^1.1.29"
bs58@^2.0.1: bs58@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d" resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d"
@ -5314,6 +5418,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000955, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz#0eb40f6c8a8c219155cbe43c4975c0efb4a0f77f" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz#0eb40f6c8a8c219155cbe43c4975c0efb4a0f77f"
integrity sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w== integrity sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==
caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30000999:
version "1.0.30000999"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==
capture-exit@^2.0.0: capture-exit@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@ -6423,10 +6532,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0" whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0" whatwg-url "^7.0.0"
date-fns@2.4.1: date-fns@2.5.0:
version "2.4.1" version "2.5.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.1.tgz#b53f9bb65ae6bd9239437035710e01cf383b625e" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.5.0.tgz#b939f17c2902ce81cffe449702ba22c0781b38ec"
integrity sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw== integrity sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==
date-now@^0.1.4: date-now@^0.1.4:
version "0.1.4" version "0.1.4"
@ -7015,6 +7124,11 @@ electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.191, electron-to-chromi
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz#f9a62a74cda77854310a2abffde8b75591ea09a1" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz#f9a62a74cda77854310a2abffde8b75591ea09a1"
integrity sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA== integrity sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==
electron-to-chromium@^1.3.247:
version "1.3.277"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.277.tgz#38b7b297f9b3f67ea900a965c1b11a555de526ec"
integrity sha512-Czmsrgng89DOgJlIknnw9bn5431QdtnUwGp5YYiPwU1DbZQUxCLF+rc1ZC09VNAdalOPcvH6AE8BaA0H5HjI/w==
element-resize-detector@^1.1.15: element-resize-detector@^1.1.15:
version "1.1.15" version "1.1.15"
resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.15.tgz#48eba1a2eaa26969a4c998d972171128c971d8d2" resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.15.tgz#48eba1a2eaa26969a4c998d972171128c971d8d2"
@ -7313,10 +7427,10 @@ eslint-plugin-import@2.18.2:
read-pkg-up "^2.0.0" read-pkg-up "^2.0.0"
resolve "^1.11.0" resolve "^1.11.0"
eslint-plugin-jest@22.17.0: eslint-plugin-jest@22.19.0:
version "22.17.0" version "22.19.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz#dc170ec8369cd1bff9c5dd8589344e3f73c88cf6" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b"
integrity sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q== integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "^1.13.0" "@typescript-eslint/experimental-utils" "^1.13.0"
@ -7335,10 +7449,10 @@ eslint-plugin-jsx-a11y@6.2.3:
has "^1.0.3" has "^1.0.3"
jsx-ast-utils "^2.2.1" jsx-ast-utils "^2.2.1"
eslint-plugin-react@7.15.0: eslint-plugin-react@7.16.0:
version "7.15.0" version "7.16.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.15.0.tgz#4808b19cf7b4c439454099d4eb8f0cf0e9fe31dd" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09"
integrity sha512-NbIh/yVXoltm8Df28PiPRanfCZAYubGqXU391MTCpW955Vum7S0nZdQYXGAvDh9ye4aNCmOR6YcYZsfMbEQZQA== integrity sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==
dependencies: dependencies:
array-includes "^3.0.3" array-includes "^3.0.3"
doctrine "^2.1.0" doctrine "^2.1.0"
@ -9104,7 +9218,7 @@ global-prefix@^3.0.0:
kind-of "^6.0.2" kind-of "^6.0.2"
which "^1.3.1" which "^1.3.1"
global@^4.3.0, global@^4.3.2: global@^4.3.0, global@^4.3.2, global@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
@ -12737,6 +12851,13 @@ node-releases@^1.1.13, node-releases@^1.1.25:
dependencies: dependencies:
semver "^5.3.0" semver "^5.3.0"
node-releases@^1.1.29:
version "1.1.34"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.34.tgz#ced4655ee1ba9c3a2c5dcbac385e19434155fd40"
integrity sha512-fNn12JTEfniTuCqo0r9jXgl44+KxRH/huV7zM/KAGOKxDKrHr6EbT7SSs4B+DNxyBE2mks28AD+Jw6PkfY5uwA==
dependencies:
semver "^6.3.0"
nopt@^4.0.1: nopt@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
@ -14042,6 +14163,11 @@ postcss-value-parser@^4.0.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ== integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
postcss-value-parser@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9"
integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.5, postcss@^7.0.6: postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.17" version "7.0.17"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
@ -14051,6 +14177,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.1
source-map "^0.6.1" source-map "^0.6.1"
supports-color "^6.1.0" supports-color "^6.1.0"
postcss@^7.0.18:
version "7.0.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
pre-commit@^1.2.2: pre-commit@^1.2.2:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6"
@ -14629,10 +14764,10 @@ react-dom@^16.8.3:
prop-types "^15.6.2" prop-types "^15.6.2"
scheduler "^0.13.6" scheduler "^0.13.6"
react-draggable@^3.3.2: react-draggable@^4.0.3:
version "3.3.2" version "4.0.3"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.2.tgz#966ef1d90f2387af3c2d8bd3516f601ea42ca359" resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.0.3.tgz#6b9f76f66431c47b9070e9b805bbc520df8ca481"
integrity sha512-oaz8a6enjbPtx5qb0oDWxtDNuybOylvto1QLydsXgKmwT7e3GXC2eMVDwEMIUYJIFqVG72XpOv673UuuAq6LhA== integrity sha512-4vD6zms+9QGeZ2RQXzlUBw8PBYUXy+dzYX5r22idjp9YwQKIIvD/EojL0rbjS1GK4C3P0rAJnmKa8gDQYWUDyA==
dependencies: dependencies:
classnames "^2.2.5" classnames "^2.2.5"
prop-types "^15.6.0" prop-types "^15.6.0"
@ -14683,10 +14818,10 @@ react-helmet-async@^1.0.2:
react-fast-compare "2.0.4" react-fast-compare "2.0.4"
shallowequal "1.1.0" shallowequal "1.1.0"
react-hot-loader@4.12.14: react-hot-loader@4.12.15:
version "4.12.14" version "4.12.15"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.14.tgz#81ca06ffda0b90aad15d6069339f73ed6428340a" resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.15.tgz#6bf3984e52edbdf02ea8952777f53da1b3c68c95"
integrity sha512-ecxH4eBvEaJ9onT8vkEmK1FAAJUh1PqzGqds9S3k+GeihSp7nKAp4fOxytO+Ghr491LiBD38jaKyDXYnnpI9pQ== integrity sha512-sgkN6g+tgPE6xZzD0Ysqll7KUFYJbMX0DrczT5OxD6S7hZlSnmqSC3ceudwCkiDd65ZTtm+Ayk4Y9k5xxCvpOw==
dependencies: dependencies:
fast-levenshtein "^2.0.6" fast-levenshtein "^2.0.6"
global "^4.3.0" global "^4.3.0"
@ -15858,6 +15993,14 @@ schema-utils@^2.0.1:
ajv "^6.1.0" ajv "^6.1.0"
ajv-keywords "^3.1.0" ajv-keywords "^3.1.0"
schema-utils@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56"
integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==
dependencies:
ajv "^6.10.2"
ajv-keywords "^3.4.1"
scrypt-js@2.0.3: scrypt-js@2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4"
@ -17040,17 +17183,18 @@ telejson@^2.2.1:
lodash.get "^4.4.2" lodash.get "^4.4.2"
memoizerific "^1.11.3" memoizerific "^1.11.3"
telejson@^2.2.2: telejson@^3.0.2:
version "2.2.2" version "3.0.3"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-2.2.2.tgz#d61d721d21849a6e4070d547aab302a9bd22c720" resolved "https://registry.yarnpkg.com/telejson/-/telejson-3.0.3.tgz#442af55f78d791d3744c9e7a696be6cdf789a4b5"
integrity sha512-YyNwnKY0ilabOwYgC/J754En1xOe5PBIUIw+C9e0+5HjVVcnQE5/gdu2yET2pmSbp5bxIDqYNjvndj2PUkIiYA== integrity sha512-gUOh6wox1zJjbGMg+e26NquZcp/F18EbIaqVvjiGqikRqVB4fYEAM8Nyin8smgwX30XhaRBOg+kCj4vInmvwAg==
dependencies: dependencies:
global "^4.3.2" "@types/is-function" "^1.0.0"
global "^4.4.0"
is-function "^1.0.1" is-function "^1.0.1"
is-regex "^1.0.4" is-regex "^1.0.4"
is-symbol "^1.0.2" is-symbol "^1.0.2"
isobject "^3.0.1" isobject "^4.0.0"
lodash "^4.17.11" lodash "^4.17.15"
memoizerific "^1.11.3" memoizerific "^1.11.3"
temp@^0.8.3: temp@^0.8.3:
@ -17865,10 +18009,10 @@ truffle-workflow-compile@^2.1.3:
truffle-external-compile "^1.0.15" truffle-external-compile "^1.0.15"
truffle-resolver "^5.0.15" truffle-resolver "^5.0.15"
truffle@5.0.39: truffle@5.0.40:
version "5.0.39" version "5.0.40"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.39.tgz#5710ba8f60a7184d9eb51d632308f2af0a2e8aff" resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.40.tgz#3fb238f0e17662df871f862981bea4109e68bf9f"
integrity sha512-2a17t4o6r0rNMpeQXBc51nXigtIaP9/sU8N2zflaazvzYgDgLMZfqh/dir2mTfyybOsrR47NL310p+6+c8u8VA== integrity sha512-UO2bVpDJRzR1oF6LgdxdpqUHdbvYJxfaqEkVDUrziIRs6Sr7CkOzHew8hexUgkqxA7wtbDmM9FNcj+9Yoa5csA==
dependencies: dependencies:
app-module-path "^2.2.0" app-module-path "^2.2.0"
mocha "5.2.0" mocha "5.2.0"
@ -18242,7 +18386,16 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
url-loader@^2.0.1, url-loader@^2.1.0: url-loader@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.2.0.tgz#af321aece1fd0d683adc8aaeb27829f29c75b46e"
integrity sha512-G8nk3np8ZAnwhHXas1JxJEwJyQdqFXAKJehfgZ/XrC48volFBRtO+FIKtF2u0Ma3bw+4vnDVjHPAQYlF9p2vsw==
dependencies:
loader-utils "^1.2.3"
mime "^2.4.4"
schema-utils "^2.4.1"
url-loader@^2.0.1:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961"
integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A== integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==
@ -19577,10 +19730,10 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webpack-bundle-analyzer@3.5.2: webpack-bundle-analyzer@3.6.0:
version "3.5.2" version "3.6.0"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.2.tgz#ac02834f4b31de8e27d71e6c7a612301ebddb79f" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd"
integrity sha512-g9spCNe25QYUVqHRDkwG414GTok2m7pTTP0wr6l0J50Z3YLS04+BGodTqqoVBL7QfU/U/9p/oiI5XFOyfZ7S/A== integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==
dependencies: dependencies:
acorn "^6.0.7" acorn "^6.0.7"
acorn-walk "^6.1.1" acorn-walk "^6.1.1"
@ -19717,10 +19870,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1:
source-list-map "^2.0.0" source-list-map "^2.0.0"
source-map "~0.6.1" source-map "~0.6.1"
webpack@4.41.0: webpack@4.41.2:
version "4.41.0" version "4.41.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e"
integrity sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g== integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==
dependencies: dependencies:
"@webassemblyjs/ast" "1.8.5" "@webassemblyjs/ast" "1.8.5"
"@webassemblyjs/helper-module-context" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5"