Merge pull request #187 from gnosis/135-notifications
Feature #135: Notifications handling
This commit is contained in:
commit
28bbf574c7
41
package.json
41
package.json
|
@ -45,6 +45,7 @@
|
||||||
"immortal-db": "^1.0.2",
|
"immortal-db": "^1.0.2",
|
||||||
"immutable": "^4.0.0-rc.9",
|
"immutable": "^4.0.0-rc.9",
|
||||||
"material-ui-search-bar": "^1.0.0-beta.13",
|
"material-ui-search-bar": "^1.0.0-beta.13",
|
||||||
|
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
|
||||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||||
"qrcode.react": "^0.9.3",
|
"qrcode.react": "^0.9.3",
|
||||||
"react": "16.9.0",
|
"react": "16.9.0",
|
||||||
|
@ -55,11 +56,11 @@
|
||||||
"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",
|
||||||
"react-router-dom": "5.1.0",
|
"react-router-dom": "^5.1.0",
|
||||||
"recompose": "^0.30.0",
|
"recompose": "^0.30.0",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"redux-actions": "^2.3.0",
|
"redux-actions": "^2.6.5",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"web3": "1.2.1"
|
"web3": "1.2.1"
|
||||||
},
|
},
|
||||||
|
@ -71,24 +72,24 @@
|
||||||
"@babel/plugin-proposal-do-expressions": "7.6.0",
|
"@babel/plugin-proposal-do-expressions": "7.6.0",
|
||||||
"@babel/plugin-proposal-export-default-from": "7.5.2",
|
"@babel/plugin-proposal-export-default-from": "7.5.2",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "7.5.2",
|
"@babel/plugin-proposal-export-namespace-from": "7.5.2",
|
||||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
"@babel/plugin-proposal-function-bind": "^7.2.0",
|
||||||
"@babel/plugin-proposal-function-sent": "7.5.0",
|
"@babel/plugin-proposal-function-sent": "7.5.0",
|
||||||
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
"@babel/plugin-proposal-json-strings": "^7.2.0",
|
||||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
"@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
|
||||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
"@babel/plugin-proposal-numeric-separator": "^7.2.0",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.6.0",
|
"@babel/plugin-proposal-optional-chaining": "7.6.0",
|
||||||
"@babel/plugin-proposal-pipeline-operator": "7.5.0",
|
"@babel/plugin-proposal-pipeline-operator": "7.5.0",
|
||||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
"@babel/plugin-syntax-import-meta": "^7.2.0",
|
||||||
"@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.2",
|
||||||
"@babel/preset-flow": "^7.0.0-beta.40",
|
"@babel/preset-flow": "^7.0.0",
|
||||||
"@babel/preset-react": "^7.0.0-beta.40",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@sambego/storybook-state": "^1.0.7",
|
"@sambego/storybook-state": "^1.3.6",
|
||||||
"@storybook/addon-actions": "5.2.1",
|
"@storybook/addon-actions": "5.2.1",
|
||||||
"@storybook/addon-knobs": "5.2.1",
|
"@storybook/addon-knobs": "5.2.1",
|
||||||
"@storybook/addon-links": "5.2.1",
|
"@storybook/addon-links": "5.2.1",
|
||||||
|
@ -99,13 +100,13 @@
|
||||||
"babel-eslint": "10.0.3",
|
"babel-eslint": "10.0.3",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
"babel-loader": "8.0.6",
|
"babel-loader": "8.0.6",
|
||||||
"babel-plugin-dynamic-import-node": "^2.2.0",
|
"babel-plugin-dynamic-import-node": "^2.3.0",
|
||||||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||||
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.6",
|
||||||
"css-loader": "3.2.0",
|
"css-loader": "3.2.0",
|
||||||
"detect-port": "^1.2.2",
|
"detect-port": "^1.3.0",
|
||||||
"eslint": "5.16.0",
|
"eslint": "6.4.0",
|
||||||
"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",
|
||||||
|
@ -118,7 +119,7 @@
|
||||||
"flow-bin": "0.108.0",
|
"flow-bin": "0.108.0",
|
||||||
"fs-extra": "8.1.0",
|
"fs-extra": "8.1.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.0.4",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
"jest-dom": "4.0.0",
|
"jest-dom": "4.0.0",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
|
@ -130,7 +131,7 @@
|
||||||
"prettier-eslint-cli": "5.0.0",
|
"prettier-eslint-cli": "5.0.0",
|
||||||
"run-with-testrpc": "0.3.1",
|
"run-with-testrpc": "0.3.1",
|
||||||
"storybook-host": "5.1.0",
|
"storybook-host": "5.1.0",
|
||||||
"storybook-router": "^0.3.3",
|
"storybook-router": "^0.3.4",
|
||||||
"style-loader": "1.0.0",
|
"style-loader": "1.0.0",
|
||||||
"truffle": "5.0.37",
|
"truffle": "5.0.37",
|
||||||
"truffle-contract": "4.0.31",
|
"truffle-contract": "4.0.31",
|
||||||
|
@ -141,6 +142,6 @@
|
||||||
"webpack-bundle-analyzer": "3.5.1",
|
"webpack-bundle-analyzer": "3.5.1",
|
||||||
"webpack-cli": "3.3.9",
|
"webpack-cli": "3.3.9",
|
||||||
"webpack-dev-server": "3.8.1",
|
"webpack-dev-server": "3.8.1",
|
||||||
"webpack-manifest-plugin": "2.1.1"
|
"webpack-manifest-plugin": "^2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@ const styles = () => ({
|
||||||
padding: 0,
|
padding: 0,
|
||||||
boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)',
|
boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)',
|
||||||
minWidth: '280px',
|
minWidth: '280px',
|
||||||
left: '4px',
|
borderRadius: '8px',
|
||||||
|
marginTop: '11px',
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
borderBottom: `solid 2px ${border}`,
|
borderBottom: `solid 2px ${border}`,
|
||||||
|
@ -47,6 +48,9 @@ const styles = () => ({
|
||||||
flexBasis: '95px',
|
flexBasis: '95px',
|
||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
},
|
},
|
||||||
|
popper: {
|
||||||
|
zIndex: 2000,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const Layout = openHoc(({
|
const Layout = openHoc(({
|
||||||
|
@ -63,17 +67,18 @@ const Layout = openHoc(({
|
||||||
<SafeListHeader />
|
<SafeListHeader />
|
||||||
<Divider />
|
<Divider />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Divider />
|
|
||||||
<Provider open={open} toggle={toggle} info={providerInfo}>
|
<Provider open={open} toggle={toggle} info={providerInfo}>
|
||||||
{(providerRef) => (
|
{(providerRef) => (
|
||||||
<Popper open={open} anchorEl={providerRef.current} placement="bottom-end">
|
<Popper open={open} anchorEl={providerRef.current} placement="bottom" className={classes.popper}>
|
||||||
{({ TransitionProps }) => (
|
{({ TransitionProps }) => (
|
||||||
<Grow {...TransitionProps}>
|
<Grow {...TransitionProps}>
|
||||||
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}>
|
<>
|
||||||
<List className={classes.root} component="div">
|
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}>
|
||||||
{providerDetails}
|
<List className={classes.root} component="div">
|
||||||
</List>
|
{providerDetails}
|
||||||
</ClickAwayListener>
|
</List>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</>
|
||||||
</Grow>
|
</Grow>
|
||||||
)}
|
)}
|
||||||
</Popper>
|
</Popper>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import IconButton from '@material-ui/core/IconButton'
|
||||||
import ExpandLess from '@material-ui/icons/ExpandLess'
|
import ExpandLess from '@material-ui/icons/ExpandLess'
|
||||||
import ExpandMore from '@material-ui/icons/ExpandMore'
|
import ExpandMore from '@material-ui/icons/ExpandMore'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
|
import Divider from '~/components/layout/Divider'
|
||||||
import { type Open } from '~/components/hoc/OpenHoc'
|
import { type Open } from '~/components/hoc/OpenHoc'
|
||||||
import { sm, md } from '~/theme/variables'
|
import { sm, md } from '~/theme/variables'
|
||||||
|
|
||||||
|
@ -20,7 +21,8 @@ const styles = () => ({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexBasis: '250px',
|
flexBasis: '284px',
|
||||||
|
marginRight: '20px',
|
||||||
},
|
},
|
||||||
provider: {
|
provider: {
|
||||||
padding: `${sm} ${md}`,
|
padding: `${sm} ${md}`,
|
||||||
|
@ -54,12 +56,14 @@ class Provider extends React.Component<Props> {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={this.myRef} className={classes.root}>
|
<div ref={this.myRef} className={classes.root}>
|
||||||
|
<Divider />
|
||||||
<Col end="sm" middle="xs" className={classes.provider} onClick={toggle}>
|
<Col end="sm" middle="xs" className={classes.provider} onClick={toggle}>
|
||||||
{info}
|
{info}
|
||||||
<IconButton disableRipple className={classes.expand}>
|
<IconButton disableRipple className={classes.expand}>
|
||||||
{open ? <ExpandLess /> : <ExpandMore />}
|
{open ? <ExpandLess /> : <ExpandMore />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Divider />
|
||||||
</div>
|
</div>
|
||||||
{children(this.myRef)}
|
{children(this.myRef)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -143,7 +143,7 @@ const UserDetails = ({
|
||||||
<Hairline margin="xs" />
|
<Hairline margin="xs" />
|
||||||
<Row className={classes.details}>
|
<Row className={classes.details}>
|
||||||
<Paragraph noMargin align="right" className={classes.labels}>
|
<Paragraph noMargin align="right" className={classes.labels}>
|
||||||
Client
|
Wallet
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{provider === 'safe'
|
{provider === 'safe'
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import { withSnackbar } from 'notistack'
|
||||||
import { logComponentStack, type Info } from '~/utils/logBoundaries'
|
import { logComponentStack, type Info } from '~/utils/logBoundaries'
|
||||||
import { SharedSnackbarConsumer, type Variant } from '~/components/SharedSnackBar'
|
|
||||||
import { WALLET_ERROR_MSG } from '~/logic/wallets/store/actions'
|
|
||||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||||
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
||||||
|
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
||||||
import ProviderAccesible from './component/ProviderInfo/ProviderAccesible'
|
import ProviderAccesible from './component/ProviderInfo/ProviderAccesible'
|
||||||
import UserDetails from './component/ProviderDetails/UserDetails'
|
import UserDetails from './component/ProviderDetails/UserDetails'
|
||||||
import ProviderDisconnected from './component/ProviderInfo/ProviderDisconnected'
|
import ProviderDisconnected from './component/ProviderInfo/ProviderDisconnected'
|
||||||
|
@ -16,7 +16,7 @@ import selector, { type SelectorProps } from './selector'
|
||||||
|
|
||||||
type Props = Actions &
|
type Props = Actions &
|
||||||
SelectorProps & {
|
SelectorProps & {
|
||||||
openSnackbar: (message: string, variant: Variant) => void,
|
enqueueSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -39,31 +39,33 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, info: Info) {
|
componentDidCatch(error: Error, info: Info) {
|
||||||
const { openSnackbar } = this.props
|
const { enqueueSnackbar, closeSnackbar } = this.props
|
||||||
|
|
||||||
this.setState({ hasError: true })
|
this.setState({ hasError: true })
|
||||||
openSnackbar(WALLET_ERROR_MSG, 'error')
|
showSnackbar(NOTIFICATIONS.CONNECT_WALLET_ERROR_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
logComponentStack(error, info)
|
logComponentStack(error, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisconnect = () => {
|
onDisconnect = () => {
|
||||||
const { removeProvider, openSnackbar } = this.props
|
const { removeProvider, enqueueSnackbar, closeSnackbar } = this.props
|
||||||
|
|
||||||
clearInterval(this.providerListener)
|
clearInterval(this.providerListener)
|
||||||
|
|
||||||
removeProvider(openSnackbar)
|
removeProvider(enqueueSnackbar, closeSnackbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnect = async () => {
|
onConnect = async () => {
|
||||||
const { fetchProvider, openSnackbar } = this.props
|
const { fetchProvider, enqueueSnackbar, closeSnackbar } = this.props
|
||||||
|
|
||||||
clearInterval(this.providerListener)
|
clearInterval(this.providerListener)
|
||||||
let currentProvider: ProviderProps = await getProviderInfo()
|
let currentProvider: ProviderProps = await getProviderInfo()
|
||||||
fetchProvider(currentProvider, openSnackbar)
|
fetchProvider(currentProvider, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
this.providerListener = setInterval(async () => {
|
this.providerListener = setInterval(async () => {
|
||||||
const newProvider: ProviderProps = await getProviderInfo()
|
const newProvider: ProviderProps = await getProviderInfo()
|
||||||
if (JSON.stringify(currentProvider) !== JSON.stringify(newProvider)) {
|
if (currentProvider && JSON.stringify(currentProvider) !== JSON.stringify(newProvider)) {
|
||||||
fetchProvider(newProvider, openSnackbar)
|
fetchProvider(newProvider, enqueueSnackbar, closeSnackbar)
|
||||||
}
|
}
|
||||||
currentProvider = newProvider
|
currentProvider = newProvider
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
@ -111,13 +113,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = connect(
|
export default connect(
|
||||||
selector,
|
selector,
|
||||||
actions,
|
actions,
|
||||||
)(HeaderComponent)
|
)(withSnackbar(HeaderComponent))
|
||||||
|
|
||||||
const HeaderSnack = () => (
|
|
||||||
<SharedSnackbarConsumer>{({ openSnackbar }) => <Header openSnackbar={openSnackbar} />}</SharedSnackbarConsumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default HeaderSnack
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
// @flow
|
||||||
|
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
||||||
|
import closeSnackbar from '~/logic/notifications/store/actions/closeSnackbar'
|
||||||
|
import removeSnackbar from '~/logic/notifications/store/actions/removeSnackbar'
|
||||||
|
|
||||||
|
export type Actions = {
|
||||||
|
enqueueSnackbar: typeof enqueueSnackbar,
|
||||||
|
closeSnackbar: typeof closeSnackbar,
|
||||||
|
removeSnackbar: typeof removeSnackbar,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
removeSnackbar,
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// @flow
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { withSnackbar } from 'notistack'
|
||||||
|
import actions from './actions'
|
||||||
|
import selector from './selector'
|
||||||
|
|
||||||
|
class Notifier extends Component {
|
||||||
|
displayed = []
|
||||||
|
|
||||||
|
shouldComponentUpdate({ notifications: newSnacks = [] }) {
|
||||||
|
const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props
|
||||||
|
|
||||||
|
if (!newSnacks.size) {
|
||||||
|
this.displayed = []
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let notExists = false
|
||||||
|
for (let i = 0; i < newSnacks.size; i += 1) {
|
||||||
|
const newSnack = newSnacks.get(i)
|
||||||
|
|
||||||
|
if (newSnack.dismissed) {
|
||||||
|
closeSnackbar(newSnack.key)
|
||||||
|
removeSnackbar(newSnack.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notExists) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).length
|
||||||
|
}
|
||||||
|
return notExists
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const { notifications = [], enqueueSnackbar, removeSnackbar } = this.props
|
||||||
|
|
||||||
|
notifications.forEach((notification) => {
|
||||||
|
// Do nothing if snackbar is already displayed
|
||||||
|
if (this.displayed.includes(notification.key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Display snackbar using notistack
|
||||||
|
enqueueSnackbar(notification.message, {
|
||||||
|
...notification.options,
|
||||||
|
onClose: (event, reason, key) => {
|
||||||
|
if (notification.options.onClose) {
|
||||||
|
notification.options.onClose(event, reason, key)
|
||||||
|
}
|
||||||
|
// Dispatch action to remove snackbar from redux store
|
||||||
|
removeSnackbar(key)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// Keep track of snackbars that we've displayed
|
||||||
|
this.storeDisplayed(notification.key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
storeDisplayed = (id) => {
|
||||||
|
this.displayed = [...this.displayed, id]
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withSnackbar(
|
||||||
|
connect(
|
||||||
|
selector,
|
||||||
|
actions,
|
||||||
|
)(Notifier),
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
// @flow
|
||||||
|
import { createStructuredSelector } from 'reselect'
|
||||||
|
import { notificationsListSelector } from '~/logic/notifications/store/selectors'
|
||||||
|
|
||||||
|
export default createStructuredSelector<Object, *>({
|
||||||
|
notifications: notificationsListSelector,
|
||||||
|
})
|
|
@ -1,105 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as React from 'react'
|
|
||||||
import { Snackbar } from '@material-ui/core'
|
|
||||||
import SnackbarContent from '~/components/SnackbarContent'
|
|
||||||
|
|
||||||
export const SharedSnackbar = () => (
|
|
||||||
<SharedSnackbarConsumer>
|
|
||||||
{(value) => {
|
|
||||||
const {
|
|
||||||
snackbarIsOpen, message, closeSnackbar, variant,
|
|
||||||
} = value
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Snackbar
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: 'bottom',
|
|
||||||
horizontal: 'right',
|
|
||||||
}}
|
|
||||||
open={snackbarIsOpen}
|
|
||||||
autoHideDuration={4000}
|
|
||||||
onClose={closeSnackbar}
|
|
||||||
>
|
|
||||||
<SnackbarContent onClose={closeSnackbar} message={message} variant={variant} />
|
|
||||||
</Snackbar>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
type SnackbarContext = {
|
|
||||||
openSnackbar: Function,
|
|
||||||
closeSnackbar: Function,
|
|
||||||
snackbarIsOpen: boolean,
|
|
||||||
message: string,
|
|
||||||
variant: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const SharedSnackbarContext = React.createContext<SnackbarContext>({
|
|
||||||
openSnackbar: undefined,
|
|
||||||
closeSnackbar: undefined,
|
|
||||||
snackbarIsOpen: false,
|
|
||||||
message: '',
|
|
||||||
variant: 'info',
|
|
||||||
})
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.Node,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Variant = 'success' | 'error' | 'warning' | 'info'
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
isOpen: boolean,
|
|
||||||
message: string,
|
|
||||||
variant: Variant,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SharedSnackbarProvider extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isOpen: false,
|
|
||||||
message: '',
|
|
||||||
variant: 'info',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openSnackbar = (message: string, variant: Variant) => {
|
|
||||||
this.setState({
|
|
||||||
message,
|
|
||||||
variant,
|
|
||||||
isOpen: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
closeSnackbar = () => {
|
|
||||||
this.setState({
|
|
||||||
message: '',
|
|
||||||
isOpen: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { children } = this.props
|
|
||||||
const { message, variant, isOpen } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SharedSnackbarContext.Provider
|
|
||||||
value={{
|
|
||||||
openSnackbar: this.openSnackbar,
|
|
||||||
closeSnackbar: this.closeSnackbar,
|
|
||||||
snackbarIsOpen: isOpen,
|
|
||||||
message,
|
|
||||||
variant,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SharedSnackbar />
|
|
||||||
{children}
|
|
||||||
</SharedSnackbarContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SharedSnackbarConsumer = SharedSnackbarContext.Consumer
|
|
|
@ -1,110 +0,0 @@
|
||||||
// @flow
|
|
||||||
import SnackbarContent from '@material-ui/core/SnackbarContent'
|
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
import * as React from 'react'
|
|
||||||
import CloseIcon from '@material-ui/icons/Close'
|
|
||||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle'
|
|
||||||
import ErrorIcon from '@material-ui/icons/Error'
|
|
||||||
import InfoIcon from '@material-ui/icons/Info'
|
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
|
||||||
import WarningIcon from '@material-ui/icons/Warning'
|
|
||||||
import { type WithStyles } from '~/theme/mui'
|
|
||||||
import {
|
|
||||||
secondary, warning, connected, error,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
|
|
||||||
type Variant = 'success' | 'error' | 'warning' | 'info'
|
|
||||||
|
|
||||||
type MessageProps = WithStyles & {
|
|
||||||
variant: Variant,
|
|
||||||
message: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = MessageProps & {
|
|
||||||
onClose?: () => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloseProps = WithStyles & {
|
|
||||||
onClose: () => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
const variantIcon = {
|
|
||||||
success: CheckCircleIcon,
|
|
||||||
warning: WarningIcon,
|
|
||||||
error: ErrorIcon,
|
|
||||||
info: InfoIcon,
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = (theme) => ({
|
|
||||||
success: {
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
},
|
|
||||||
successIcon: {
|
|
||||||
color: connected,
|
|
||||||
},
|
|
||||||
warning: {
|
|
||||||
backgroundColor: '#fff3e2',
|
|
||||||
},
|
|
||||||
warningIcon: {
|
|
||||||
color: warning,
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
backgroundColor: '#ffe6ea',
|
|
||||||
},
|
|
||||||
errorIcon: {
|
|
||||||
color: error,
|
|
||||||
},
|
|
||||||
info: {
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
},
|
|
||||||
infoIcon: {
|
|
||||||
color: secondary,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
iconVariant: {
|
|
||||||
opacity: 0.9,
|
|
||||||
marginRight: theme.spacing(1),
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const Close = ({ classes, onClose }: CloseProps) => (
|
|
||||||
<IconButton key="close" aria-label="Close" color="inherit" className={classes.close} onClick={onClose}>
|
|
||||||
<CloseIcon className={classes.icon} />
|
|
||||||
</IconButton>
|
|
||||||
)
|
|
||||||
|
|
||||||
const Message = ({ classes, message, variant }: MessageProps) => {
|
|
||||||
const Icon = variantIcon[variant]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span id="client-snackbar" className={classes.message}>
|
|
||||||
<Icon className={classNames(classes.icon, classes.iconVariant, classes[`${variant}Icon`])} />
|
|
||||||
{message}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const GnoSnackbarContent = ({
|
|
||||||
variant, classes, message, onClose,
|
|
||||||
}: Props) => {
|
|
||||||
const action = onClose ? [<Close key="close" onClose={onClose} classes={classes} />] : undefined
|
|
||||||
const messageComponent = <Message classes={classes} message={message} variant={variant} />
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SnackbarContent
|
|
||||||
className={classNames(classes[variant])}
|
|
||||||
aria-describedby="client-snackbar"
|
|
||||||
message={messageComponent}
|
|
||||||
action={action}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withStyles(styles)(GnoSnackbarContent)
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="18" viewBox="0 0 22 18">
|
||||||
|
<g fill="none" fill-rule="nonzero">
|
||||||
|
<path fill="#FFC05F" d="M1.734 18h18.532a1 1 0 0 0 .865-1.501L11.865.495a1 1 0 0 0-1.73 0L.869 16.499A1 1 0 0 0 1.734 18z"/>
|
||||||
|
<path fill="#FFF" d="M12 12h-2V6h2zM12 15h-2v-2h2z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 335 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="20" viewBox="0 0 22 20">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#008C73" d="M11 0C5.489 0 1 4.489 1 10s4.489 10 10 10 10-4.489 10-10S16.511 0 11 0z"/>
|
||||||
|
<path fill="#FFF" d="M10.124 13.75L6 9.406l1.245-1.312 2.88 3.034 4.63-4.878L16 7.561z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 345 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#F02525" fill-rule="nonzero" d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0z"/>
|
||||||
|
<path fill="#FFF" d="M11 15H9v-2h2zM11 11H9V5h2z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 322 B |
|
@ -1,23 +1,85 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import Header from '~/components/Header'
|
import { SnackbarProvider } from 'notistack'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import SidebarProvider from '~/components/Sidebar'
|
import SidebarProvider from '~/components/Sidebar'
|
||||||
import { SharedSnackbarProvider } from '~/components/SharedSnackBar'
|
import Header from '~/components/Header'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
|
import Notifier from '~/components/Notifier'
|
||||||
|
import AlertLogo from './assets/alert.svg'
|
||||||
|
import CheckLogo from './assets/check.svg'
|
||||||
|
import ErrorLogo from './assets/error.svg'
|
||||||
import styles from './index.scss'
|
import styles from './index.scss'
|
||||||
|
|
||||||
|
const notificationStyles = {
|
||||||
|
success: {
|
||||||
|
background: '#ffffff',
|
||||||
|
fontFamily: 'Averta',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: 1.43,
|
||||||
|
color: '#001428',
|
||||||
|
minHeight: '58px',
|
||||||
|
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
background: '#ffe6ea',
|
||||||
|
fontFamily: 'Averta',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: 1.43,
|
||||||
|
color: '#001428',
|
||||||
|
minHeight: '58px',
|
||||||
|
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
background: '#fff3e2',
|
||||||
|
fontFamily: 'Averta',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: 1.43,
|
||||||
|
color: '#001428',
|
||||||
|
minHeight: '58px',
|
||||||
|
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
background: '#e8673c',
|
||||||
|
fontFamily: 'Averta',
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: 1.43,
|
||||||
|
color: '#ffffff',
|
||||||
|
minHeight: '58px',
|
||||||
|
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
|
classes: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageFrame = ({ children }: Props) => (
|
const PageFrame = ({ children, classes }: Props) => (
|
||||||
<SharedSnackbarProvider>
|
<div className={styles.frame}>
|
||||||
<div className={styles.frame}>
|
<SnackbarProvider
|
||||||
|
maxSnack={5}
|
||||||
|
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
classes={{
|
||||||
|
variantSuccess: classes.success,
|
||||||
|
variantError: classes.error,
|
||||||
|
variantWarning: classes.warning,
|
||||||
|
variantInfo: classes.info,
|
||||||
|
}}
|
||||||
|
iconVariant={{
|
||||||
|
success: <Img src={CheckLogo} alt="Success" />,
|
||||||
|
error: <Img src={ErrorLogo} alt="Error" />,
|
||||||
|
warning: <Img src={AlertLogo} alt="Warning" />,
|
||||||
|
info: '',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Notifier />
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<Header />
|
<Header />
|
||||||
{children}
|
{children}
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</div>
|
</SnackbarProvider>
|
||||||
</SharedSnackbarProvider>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default PageFrame
|
export default withStyles(notificationStyles)(PageFrame)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
export * from './notificationTypes'
|
||||||
|
export * from './notificationBuilder'
|
|
@ -0,0 +1,159 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { IconButton } from '@material-ui/core'
|
||||||
|
import { Close as IconClose } from '@material-ui/icons'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
|
import { type Notification, NOTIFICATIONS } from './notificationTypes'
|
||||||
|
|
||||||
|
type NotificationsQueue = {
|
||||||
|
beforeExecution: Notification,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: Notification,
|
||||||
|
moreConfirmationsNeeded: Notification,
|
||||||
|
},
|
||||||
|
afterExecution: Notification,
|
||||||
|
afterExecutionError: Notification,
|
||||||
|
afterRejection: Notification,
|
||||||
|
}
|
||||||
|
|
||||||
|
const standardTxNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: NOTIFICATIONS.SIGN_TX_MSG,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: NOTIFICATIONS.TX_PENDING_MSG,
|
||||||
|
moreConfirmationsNeeded: NOTIFICATIONS.TX_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||||
|
},
|
||||||
|
afterRejection: NOTIFICATIONS.TX_REJECTED_MSG,
|
||||||
|
afterExecution: NOTIFICATIONS.TX_EXECUTED_MSG,
|
||||||
|
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmationTxNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: NOTIFICATIONS.SIGN_TX_MSG,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: NOTIFICATIONS.TX_CONFIRMATION_PENDING_MSG,
|
||||||
|
moreConfirmationsNeeded: null,
|
||||||
|
},
|
||||||
|
afterRejection: NOTIFICATIONS.TX_REJECTED_MSG,
|
||||||
|
afterExecution: NOTIFICATIONS.TX_CONFIRMATION_EXECUTED_MSG,
|
||||||
|
afterExecutionError: NOTIFICATIONS.TX_CONFIRMATION_FAILED_MSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancellationTxNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: NOTIFICATIONS.SIGN_TX_MSG,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: NOTIFICATIONS.TX_PENDING_MSG,
|
||||||
|
moreConfirmationsNeeded: NOTIFICATIONS.TX_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||||
|
},
|
||||||
|
afterRejection: NOTIFICATIONS.TX_REJECTED_MSG,
|
||||||
|
afterExecution: NOTIFICATIONS.TX_EXECUTED_MSG,
|
||||||
|
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerChangeTxNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: NOTIFICATIONS.SIGN_OWNER_CHANGE_MSG,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: NOTIFICATIONS.ONWER_CHANGE_PENDING_MSG,
|
||||||
|
moreConfirmationsNeeded: NOTIFICATIONS.ONWER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||||
|
},
|
||||||
|
afterRejection: NOTIFICATIONS.ONWER_CHANGE_REJECTED_MSG,
|
||||||
|
afterExecution: NOTIFICATIONS.OWNER_CHANGE_EXECUTED_MSG,
|
||||||
|
afterExecutionError: NOTIFICATIONS.ONWER_CHANGE_FAILED_MSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeNameChangeNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: null,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: null,
|
||||||
|
moreConfirmationsNeeded: null,
|
||||||
|
},
|
||||||
|
afterRejection: null,
|
||||||
|
afterExecution: NOTIFICATIONS.SAFE_NAME_CHANGE_EXECUTED_MSG,
|
||||||
|
afterExecutionError: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerNameChangeNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: null,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: null,
|
||||||
|
moreConfirmationsNeeded: null,
|
||||||
|
},
|
||||||
|
afterRejection: null,
|
||||||
|
afterExecution: NOTIFICATIONS.OWNER_NAME_CHANGE_EXECUTED_MSG,
|
||||||
|
afterExecutionError: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const thresholdChangeTxNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: NOTIFICATIONS.SIGN_THRESHOLD_CHANGE_MSG,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: NOTIFICATIONS.THRESHOLD_CHANGE_PENDING_MSG,
|
||||||
|
moreConfirmationsNeeded: NOTIFICATIONS.THRESHOLD_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||||
|
},
|
||||||
|
afterRejection: NOTIFICATIONS.THRESHOLD_CHANGE_REJECTED_MSG,
|
||||||
|
afterExecution: NOTIFICATIONS.THRESHOLD_CHANGE_EXECUTED_MSG,
|
||||||
|
afterExecutionError: NOTIFICATIONS.THRESHOLD_CHANGE_FAILED_MSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultNotificationsQueue: NotificationsQueue = {
|
||||||
|
beforeExecution: NOTIFICATIONS.SIGN_TX_MSG,
|
||||||
|
pendingExecution: {
|
||||||
|
noMoreConfirmationsNeeded: NOTIFICATIONS.TX_PENDING_MSG,
|
||||||
|
moreConfirmationsNeeded: NOTIFICATIONS.TX_PENDING_MORE_CONFIRMATIONS_MSG,
|
||||||
|
},
|
||||||
|
afterRejection: NOTIFICATIONS.TX_REJECTED_MSG,
|
||||||
|
afterExecution: NOTIFICATIONS.TX_EXECUTED_MSG,
|
||||||
|
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNofiticationsFromTxType = (txType: string) => {
|
||||||
|
let notificationsQueue: NotificationsQueue
|
||||||
|
|
||||||
|
switch (txType) {
|
||||||
|
case TX_NOTIFICATION_TYPES.STANDARD_TX: {
|
||||||
|
notificationsQueue = standardTxNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TX_NOTIFICATION_TYPES.CONFIRMATION_TX: {
|
||||||
|
notificationsQueue = confirmationTxNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TX_NOTIFICATION_TYPES.CANCELLATION_TX: {
|
||||||
|
notificationsQueue = cancellationTxNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TX_NOTIFICATION_TYPES.OWNER_CHANGE_TX: {
|
||||||
|
notificationsQueue = ownerChangeTxNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX: {
|
||||||
|
notificationsQueue = safeNameChangeNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TX_NOTIFICATION_TYPES.OWNER_NAME_CHANGE_TX: {
|
||||||
|
notificationsQueue = ownerNameChangeNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case TX_NOTIFICATION_TYPES.THRESHOLD_CHANGE_TX: {
|
||||||
|
notificationsQueue = thresholdChangeTxNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
notificationsQueue = defaultNotificationsQueue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return notificationsQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showSnackbar = (
|
||||||
|
notification: Notification,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
|
) => enqueueSnackbar(notification.message, {
|
||||||
|
...notification.options,
|
||||||
|
action: (key) => (
|
||||||
|
<IconButton onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconClose />
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
})
|
|
@ -0,0 +1,224 @@
|
||||||
|
// @flow
|
||||||
|
export const SUCCESS = 'success'
|
||||||
|
export const ERROR = 'error'
|
||||||
|
export const WARNING = 'warning'
|
||||||
|
export const INFO = 'info'
|
||||||
|
|
||||||
|
const shortDuration = 5000
|
||||||
|
const longDuration = 10000
|
||||||
|
|
||||||
|
export type Variant = SUCCESS | ERROR | WARNING | INFO
|
||||||
|
|
||||||
|
export type Notification = {
|
||||||
|
message: string,
|
||||||
|
options: {
|
||||||
|
variant: Variant,
|
||||||
|
persist: boolean,
|
||||||
|
autoHideDuration?: shortDuration | longDuration,
|
||||||
|
preventDuplicate: boolean,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Notifications = {
|
||||||
|
// Wallet Connection
|
||||||
|
CONNECT_WALLET_MSG: Notification,
|
||||||
|
CONNECT_WALLET_READ_MODE_MSG: Notification,
|
||||||
|
WALLET_CONNECTED_MSG: Notification,
|
||||||
|
WALLET_DISCONNECTED_MSG: Notification,
|
||||||
|
UNLOCK_WALLET_MSG: Notification,
|
||||||
|
CONNECT_WALLET_ERROR_MSG: Notification,
|
||||||
|
|
||||||
|
// Regular/Custom Transactions
|
||||||
|
SIGN_TX_MSG: Notification,
|
||||||
|
TX_PENDING_MSG: Notification,
|
||||||
|
TX_PENDING_MORE_CONFIRMATIONS_MSG: Notification,
|
||||||
|
TX_REJECTED_MSG: Notification,
|
||||||
|
TX_EXECUTED_MSG: Notification,
|
||||||
|
TX_FAILED_MSG: Notification,
|
||||||
|
|
||||||
|
// Approval Transactions
|
||||||
|
TX_CONFIRMATION_PENDING_MSG: Notification,
|
||||||
|
TX_CONFIRMATION_EXECUTED_MSG: Notification,
|
||||||
|
TX_CONFIRMATION_FAILED_MSG: Notification,
|
||||||
|
|
||||||
|
// Safe Name
|
||||||
|
SAFE_NAME_CHANGED_MSG: Notification,
|
||||||
|
|
||||||
|
// Owner Name
|
||||||
|
OWNER_NAME_CHANGE_EXECUTED_MSG: Notification,
|
||||||
|
|
||||||
|
// Owners
|
||||||
|
SIGN_OWNER_CHANGE_MSG: Notification,
|
||||||
|
ONWER_CHANGE_PENDING_MSG: Notification,
|
||||||
|
ONWER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: Notification,
|
||||||
|
ONWER_CHANGE_REJECTED_MSG: Notification,
|
||||||
|
ONWER_CHANGE_EXECUTED_MSG: Notification,
|
||||||
|
ONWER_CHANGE_FAILED_MSG: Notification,
|
||||||
|
|
||||||
|
// Threshold
|
||||||
|
SIGN_THRESHOLD_CHANGE_MSG: Notification,
|
||||||
|
THRESHOLD_CHANGE_PENDING_MSG: Notification,
|
||||||
|
THRESHOLD_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: Notification,
|
||||||
|
THRESHOLD_CHANGE_REJECTED_MSG: Notification,
|
||||||
|
THRESHOLD_CHANGE_EXECUTED_MSG: Notification,
|
||||||
|
THRESHOLD_CHANGE_FAILED_MSG: Notification,
|
||||||
|
|
||||||
|
// Rinkeby version
|
||||||
|
RINKEBY_VERSION_MSG: Notification,
|
||||||
|
WRONG_NETWORK_RINKEBY_MSG: Notification,
|
||||||
|
WRONG_NETWOEK_MAINNET_MSG: Notification,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NOTIFICATIONS: Notifications = {
|
||||||
|
// Wallet Connection
|
||||||
|
CONNECT_WALLET_MSG: {
|
||||||
|
message: 'Please connect wallet to continue',
|
||||||
|
options: { variant: WARNING, persist: true, preventDuplicate: true },
|
||||||
|
},
|
||||||
|
CONNECT_WALLET_READ_MODE_MSG: {
|
||||||
|
message: 'You are in read-only mode: Please connect wallet',
|
||||||
|
options: { variant: WARNING, persist: true, preventDuplicate: true },
|
||||||
|
},
|
||||||
|
WALLET_CONNECTED_MSG: {
|
||||||
|
message: 'Wallet connected',
|
||||||
|
options: {
|
||||||
|
variant: SUCCESS,
|
||||||
|
persist: false,
|
||||||
|
autoHideDuration: shortDuration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WALLET_DISCONNECTED_MSG: {
|
||||||
|
message: 'Wallet disconnected',
|
||||||
|
options: {
|
||||||
|
variant: SUCCESS,
|
||||||
|
persist: false,
|
||||||
|
autoHideDuration: shortDuration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UNLOCK_WALLET_MSG: {
|
||||||
|
message: 'Unlock your wallet to connect',
|
||||||
|
options: { variant: WARNING, persist: true, preventDuplicate: true },
|
||||||
|
},
|
||||||
|
CONNECT_WALLET_ERROR_MSG: {
|
||||||
|
message: 'Error connecting to your wallet',
|
||||||
|
options: { variant: ERROR, persist: true },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Regular/Custom Transactions
|
||||||
|
SIGN_TX_MSG: {
|
||||||
|
message: 'Please sign the transaction',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
TX_PENDING_MSG: {
|
||||||
|
message: 'Transaction pending',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
TX_PENDING_MORE_CONFIRMATIONS_MSG: {
|
||||||
|
message: 'Transaction pending: More confirmations required to execute',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
TX_REJECTED_MSG: {
|
||||||
|
message: 'Transaction rejected',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
TX_EXECUTED_MSG: {
|
||||||
|
message: 'Transaction successfully executed',
|
||||||
|
options: { variant: SUCCESS, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
TX_FAILED_MSG: {
|
||||||
|
message: 'Transaction failed',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Approval Transactions
|
||||||
|
TX_CONFIRMATION_PENDING_MSG: {
|
||||||
|
message: 'Confirmation transaction pending',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
TX_CONFIRMATION_EXECUTED_MSG: {
|
||||||
|
message: 'Confirmation transaction succesful',
|
||||||
|
options: { variant: SUCCESS, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
TX_CONFIRMATION_FAILED_MSG: {
|
||||||
|
message: 'Confirmation transaction failed',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Safe Name
|
||||||
|
SAFE_NAME_CHANGE_EXECUTED_MSG: {
|
||||||
|
message: 'Safe name changed',
|
||||||
|
options: { variant: SUCCESS, persist: false, autoHideDuration: shortDuration },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Owner Name
|
||||||
|
OWNER_NAME_CHANGE_EXECUTED_MSG: {
|
||||||
|
message: 'Owner name changed',
|
||||||
|
options: { variant: SUCCESS, persist: false, autoHideDuration: shortDuration },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Owners
|
||||||
|
SIGN_OWNER_CHANGE_MSG: {
|
||||||
|
message: 'Please sign the owner change',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
ONWER_CHANGE_PENDING_MSG: {
|
||||||
|
message: 'Owner change pending',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
ONWER_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: {
|
||||||
|
message: 'Owner change pending: More confirmations required to execute',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
ONWER_CHANGE_REJECTED_MSG: {
|
||||||
|
message: 'Owner change rejected',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
OWNER_CHANGE_EXECUTED_MSG: {
|
||||||
|
message: 'Owner change successfully executed',
|
||||||
|
options: { variant: SUCCESS, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
ONWER_CHANGE_FAILED_MSG: {
|
||||||
|
message: 'Owner change failed',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Threshold
|
||||||
|
SIGN_THRESHOLD_CHANGE_MSG: {
|
||||||
|
message: 'Please sign the required confirmations change',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
THRESHOLD_CHANGE_PENDING_MSG: {
|
||||||
|
message: 'Required confirmations change pending',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
THRESHOLD_CHANGE_PENDING_MORE_CONFIRMATIONS_MSG: {
|
||||||
|
message: 'Required confirmations change pending: More confirmations required to execute',
|
||||||
|
options: { variant: SUCCESS, persist: true },
|
||||||
|
},
|
||||||
|
THRESHOLD_CHANGE_REJECTED_MSG: {
|
||||||
|
message: 'Required confirmations change rejected',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
THRESHOLD_CHANGE_EXECUTED_MSG: {
|
||||||
|
message: 'Required confirmations change successfully executed',
|
||||||
|
options: { variant: SUCCESS, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
THRESHOLD_CHANGE_FAILED_MSG: {
|
||||||
|
message: 'Required confirmations change failed',
|
||||||
|
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Network
|
||||||
|
RINKEBY_VERSION_MSG: {
|
||||||
|
message: "Rinkeby Version: Don't send mainnet assets to this Safe",
|
||||||
|
options: { variant: INFO, persist: true, preventDuplicate: true },
|
||||||
|
},
|
||||||
|
WRONG_NETWORK_RINKEBY_MSG: {
|
||||||
|
message: 'Wrong network: Please use Rinkeby',
|
||||||
|
options: { variant: WARNING, persist: true, preventDuplicate: true },
|
||||||
|
},
|
||||||
|
WRONG_NETWOEK_MAINNET_MSG: {
|
||||||
|
message: 'Wrong network: Please use Mainnet',
|
||||||
|
options: { variant: WARNING, persist: true, preventDuplicate: true },
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
import { createAction } from 'redux-actions'
|
||||||
|
|
||||||
|
export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR'
|
||||||
|
|
||||||
|
const closeSnackbar = createAction<string, *>(CLOSE_SNACKBAR)
|
||||||
|
|
||||||
|
export default closeSnackbar
|
|
@ -0,0 +1,22 @@
|
||||||
|
// @flow
|
||||||
|
import { createAction } from 'redux-actions'
|
||||||
|
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
|
||||||
|
import { type GlobalState } from '~/store'
|
||||||
|
import { type NotificationProps } from '~/logic/notifications/store/models/notification'
|
||||||
|
|
||||||
|
export const ENQUEUE_SNACKBAR = 'ENQUEUE_SNACKBAR'
|
||||||
|
|
||||||
|
const addSnackbar = createAction<string, *>(ENQUEUE_SNACKBAR)
|
||||||
|
|
||||||
|
const enqueueSnackbar = (notification: NotificationProps) => (
|
||||||
|
dispatch: ReduxDispatch<GlobalState>,
|
||||||
|
getState: GetState<GlobalState>,
|
||||||
|
) => {
|
||||||
|
const newNotification = {
|
||||||
|
...notification,
|
||||||
|
key: new Date().getTime(),
|
||||||
|
}
|
||||||
|
dispatch(addSnackbar(newNotification))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default enqueueSnackbar
|
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
import { createAction } from 'redux-actions'
|
||||||
|
|
||||||
|
export const REMOVE_SNACKBAR = 'REMOVE_SNACKBAR'
|
||||||
|
|
||||||
|
const removeSnackbar = createAction<string, *>(REMOVE_SNACKBAR)
|
||||||
|
|
||||||
|
export default removeSnackbar
|
|
@ -0,0 +1,19 @@
|
||||||
|
// @flow
|
||||||
|
import { Record } from 'immutable'
|
||||||
|
import type { RecordFactory, RecordOf } from 'immutable'
|
||||||
|
|
||||||
|
export type NotificationProps = {
|
||||||
|
key?: number,
|
||||||
|
message: string,
|
||||||
|
options: Object,
|
||||||
|
dismissed: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeNotification: RecordFactory<NotificationProps> = Record({
|
||||||
|
key: 0,
|
||||||
|
message: '',
|
||||||
|
options: {},
|
||||||
|
dismissed: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type Notification = RecordOf<NotificationProps>
|
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
import { Map } from 'immutable'
|
||||||
|
import { handleActions, type ActionType } from 'redux-actions'
|
||||||
|
import { makeNotification, type NotificationProps } from '~/logic/notifications/store/models/notification'
|
||||||
|
import { ENQUEUE_SNACKBAR } from '../actions/enqueueSnackbar'
|
||||||
|
import { CLOSE_SNACKBAR } from '../actions/closeSnackbar'
|
||||||
|
import { REMOVE_SNACKBAR } from '../actions/removeSnackbar'
|
||||||
|
|
||||||
|
export const NOTIFICATIONS_REDUCER_ID = 'notifications'
|
||||||
|
|
||||||
|
export type NotificationReducerState = Map<string, *>
|
||||||
|
|
||||||
|
export default handleActions<NotificationReducerState, *>(
|
||||||
|
{
|
||||||
|
[ENQUEUE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
|
||||||
|
const notification: NotificationProps = action.payload
|
||||||
|
|
||||||
|
return state.set(notification.key, makeNotification(notification))
|
||||||
|
},
|
||||||
|
[CLOSE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
|
||||||
|
const { notification }: { notification: NotificationProps } = action.payload
|
||||||
|
notification.dismissed = true
|
||||||
|
|
||||||
|
return state.update(notification.key, (prev) => prev.merge(notification))
|
||||||
|
},
|
||||||
|
[REMOVE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
|
||||||
|
const key = action.payload
|
||||||
|
|
||||||
|
return state.delete(key)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Map({
|
||||||
|
notifications: Map(),
|
||||||
|
}),
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
import { List, Map } from 'immutable'
|
||||||
|
import { createSelector, type Selector } from 'reselect'
|
||||||
|
import { type GlobalState } from '~/store'
|
||||||
|
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
|
||||||
|
import { type Notification } from '~/logic/notifications/store/models/notification'
|
||||||
|
|
||||||
|
export const notificationsMapSelector = (
|
||||||
|
state: GlobalState,
|
||||||
|
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]
|
||||||
|
|
||||||
|
export const notificationsListSelector: Selector<GlobalState, {}, List<Notification>> = createSelector(
|
||||||
|
notificationsMapSelector,
|
||||||
|
(notifications: Map<string, Notification>): List<Notification> => notifications.toList(),
|
||||||
|
)
|
|
@ -82,8 +82,7 @@ export const generateTxGasEstimateFrom = async (
|
||||||
// Add 10k else we will fail in case of nested calls
|
// Add 10k else we will fail in case of nested calls
|
||||||
return txGasEstimate.toNumber() + 10000
|
return txGasEstimate.toNumber() + 10000
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line
|
console.error('Error calculating tx gas estimation', error)
|
||||||
console.log('Error calculating tx gas estimation ' + error)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,8 +127,7 @@ export const calculateTxFee = async (
|
||||||
|
|
||||||
return estimate
|
return estimate
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line
|
console.error('Error calculating tx gas estimation', error)
|
||||||
console.log('Error calculating tx gas estimation ' + error)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
export * from './gas'
|
export * from './gas'
|
||||||
export * from './send'
|
export * from './send'
|
||||||
export * from './safeBlockchainOperations'
|
|
||||||
export * from './safeTxSignerEIP712'
|
export * from './safeTxSignerEIP712'
|
||||||
export * from './txHistory'
|
export * from './txHistory'
|
||||||
export * from './notifications'
|
export * from './notifiedTransactions'
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
// @flow
|
|
||||||
export type Notifications = {
|
|
||||||
BEFORE_EXECUTION_OR_CREATION: string,
|
|
||||||
AFTER_EXECUTION: string,
|
|
||||||
CREATED_MORE_CONFIRMATIONS_NEEDED: string,
|
|
||||||
ERROR: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_NOTIFICATIONS: Notifications = {
|
|
||||||
BEFORE_EXECUTION_OR_CREATION: 'Transaction in progress',
|
|
||||||
AFTER_EXECUTION: 'Transaction successfully executed',
|
|
||||||
CREATED_MORE_CONFIRMATIONS_NEEDED: 'Transaction in progress: More confirmations required to execute',
|
|
||||||
ERROR: 'Transaction failed',
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export type NotifiedTransaction = {
|
||||||
|
STANDARD_TX: string,
|
||||||
|
CONFIRMATION_TX: string,
|
||||||
|
CANCELLATION_TX: string,
|
||||||
|
OWNER_CHANGE_TX: string,
|
||||||
|
SAFE_NAME_CHANGE_TX: string,
|
||||||
|
OWNER_NAME_CHANGE_TX: string,
|
||||||
|
THRESHOLD_CHANGE_TX: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TX_NOTIFICATION_TYPES: NotifiedTransaction = {
|
||||||
|
STANDARD_TX: 'STANDARD_TX',
|
||||||
|
CONFIRMATION_TX: 'CONFIRMATION_TX',
|
||||||
|
CANCELLATION_TX: 'CANCELLATION_TX',
|
||||||
|
OWNER_CHANGE_TX: 'OWNER_CHANGE_TX',
|
||||||
|
SAFE_NAME_CHANGE_TX: 'SAFE_NAME_CHANGE_TX',
|
||||||
|
OWNER_NAME_CHANGE_TX: 'OWNER_NAME_CHANGE_TX',
|
||||||
|
THRESHOLD_CHANGE_TX: 'THRESHOLD_CHANGE_TX',
|
||||||
|
}
|
|
@ -1,142 +0,0 @@
|
||||||
// // @flow
|
|
||||||
// import { List } from 'immutable'
|
|
||||||
// import { calculateGasOf, checkReceiptStatus, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
|
||||||
// import { type Operation, saveTxToHistory } from '~/logic/safe/transactions'
|
|
||||||
// import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
|
||||||
// import { buildSignaturesFrom } from '~/logic/safe/safeTxSigner'
|
|
||||||
// import { generateMetamaskSignature, generateTxGasEstimateFrom, estimateDataGas } from '~/logic/safe/transactions'
|
|
||||||
// import { storeSignature, getSignaturesFrom } from '~/utils/storage/signatures'
|
|
||||||
// import { signaturesViaMetamask } from '~/config'
|
|
||||||
|
|
||||||
// export const approveTransaction = async (
|
|
||||||
// safeAddress: string,
|
|
||||||
// to: string,
|
|
||||||
// valueInWei: number,
|
|
||||||
// data: string,
|
|
||||||
// operation: Operation,
|
|
||||||
// nonce: number,
|
|
||||||
// sender: string,
|
|
||||||
// ) => {
|
|
||||||
// const gasPrice = await calculateGasPrice()
|
|
||||||
|
|
||||||
// if (signaturesViaMetamask()) {
|
|
||||||
// // return executeTransaction(safeAddress, to, valueInWei, data, operation, nonce, sender)
|
|
||||||
// const safe = await getGnosisSafeInstanceAt(safeAddress)
|
|
||||||
// const txGasEstimate = await generateTxGasEstimateFrom(safe, safeAddress, data, to, valueInWei, operation)
|
|
||||||
// const signature = await generateMetamaskSignature(
|
|
||||||
// safe,
|
|
||||||
// safeAddress,
|
|
||||||
// // sender
|
|
||||||
// to,
|
|
||||||
// valueInWei,
|
|
||||||
// nonce,
|
|
||||||
// data,
|
|
||||||
// operation,
|
|
||||||
// txGasEstimate,
|
|
||||||
// )
|
|
||||||
// storeSignature(safeAddress, nonce, signature)
|
|
||||||
|
|
||||||
// return undefined
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
|
||||||
// const contractTxHash = await gnosisSafe.getTransactionHash(to, valueInWei, data, operation, 0, 0, 0, 0, 0, nonce)
|
|
||||||
|
|
||||||
// const approveData = gnosisSafe.contract.methods.approveHash(contractTxHash).encodeABI()
|
|
||||||
// const gas = await calculateGasOf(approveData, sender, safeAddress)
|
|
||||||
// const txReceipt = await gnosisSafe.approveHash(contractTxHash, { from: sender, gas, gasPrice })
|
|
||||||
|
|
||||||
// const txHash = txReceipt.tx
|
|
||||||
// await checkReceiptStatus(txHash)
|
|
||||||
|
|
||||||
// await saveTxToHistory(safeAddress, to, valueInWei, data, operation, nonce, txHash, sender, 'confirmation')
|
|
||||||
|
|
||||||
// return txHash
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // export const executeTransaction = async (
|
|
||||||
// // safeAddress: string,
|
|
||||||
// // to: string,
|
|
||||||
// // valueInWei: number,
|
|
||||||
// // data: string,
|
|
||||||
// // operation: Operation,
|
|
||||||
// // nonce: number,
|
|
||||||
// // sender: string,
|
|
||||||
// // ownersWhoHasSigned: List<string>,
|
|
||||||
// // ) => {
|
|
||||||
// // const gasPrice = await calculateGasPrice()
|
|
||||||
|
|
||||||
// // if (signaturesViaMetamask()) {
|
|
||||||
// // const safe = await getSafeEthereumInstance(safeAddress)
|
|
||||||
// // const txGasEstimate = await generateTxGasEstimateFrom(safe, safeAddress, data, to, valueInWei, operation)
|
|
||||||
// // const signature = await generateMetamaskSignature(
|
|
||||||
// // safe,
|
|
||||||
// // safeAddress,
|
|
||||||
// // sender,
|
|
||||||
// // to,
|
|
||||||
// // valueInWei,
|
|
||||||
// // nonce,
|
|
||||||
// // data,
|
|
||||||
// // operation,
|
|
||||||
// // txGasEstimate,
|
|
||||||
// // )
|
|
||||||
// // storeSignature(safeAddress, nonce, signature)
|
|
||||||
|
|
||||||
// // const sigs = getSignaturesFrom(safeAddress, nonce)
|
|
||||||
// // const threshold = await safe.getThreshold()
|
|
||||||
// // const gas = await estimateDataGas(
|
|
||||||
// // safe,
|
|
||||||
// // to,
|
|
||||||
// // valueInWei,
|
|
||||||
// // data,
|
|
||||||
// // operation,
|
|
||||||
// // txGasEstimate,
|
|
||||||
// // 0,
|
|
||||||
// // nonce,
|
|
||||||
// // Number(threshold),
|
|
||||||
// // 0,
|
|
||||||
// // )
|
|
||||||
// // const numOwners = await safe.getOwners()
|
|
||||||
// // const gasIncludingRemovingStoreUpfront = gas + txGasEstimate + numOwners.length * 15000
|
|
||||||
|
|
||||||
// // const txReceipt = await safe.execTransaction(
|
|
||||||
// // to,
|
|
||||||
// // valueInWei,
|
|
||||||
// // data,
|
|
||||||
// // operation,
|
|
||||||
// // txGasEstimate,
|
|
||||||
// // 0, // dataGasEstimate
|
|
||||||
// // 0, // gasPrice
|
|
||||||
// // 0, // txGasToken
|
|
||||||
// // 0, // refundReceiver
|
|
||||||
// // sigs,
|
|
||||||
// // { from: sender, gas: gasIncludingRemovingStoreUpfront, gasPrice },
|
|
||||||
// // )
|
|
||||||
|
|
||||||
// // const txHash = txReceipt.tx
|
|
||||||
// // await checkReceiptStatus(txHash)
|
|
||||||
// // // await submitOperation(safeAddress, to, valueInWei, data, operation, nonce, txHash, sender, 'execution')
|
|
||||||
|
|
||||||
// // return txHash
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
|
||||||
// // const signatures = buildSignaturesFrom(ownersWhoHasSigned, sender)
|
|
||||||
// // const txExecutionData = gnosisSafe.contract.methods
|
|
||||||
// // .execTransaction(to, valueInWei, data, operation, 0, 0, 0, 0, 0, signatures)
|
|
||||||
// // .encodeABI()
|
|
||||||
// // const gas = await calculateGasOf(txExecutionData, sender, safeAddress)
|
|
||||||
// // const numOwners = await gnosisSafe.getOwners()
|
|
||||||
// // const gasIncludingRemovingStoreUpfront = gas + numOwners.length * 15000
|
|
||||||
// // const txReceipt = await gnosisSafe.execTransaction(to, valueInWei, data, operation, 0, 0, 0, 0, 0, signatures, {
|
|
||||||
// // from: sender,
|
|
||||||
// // gas: gasIncludingRemovingStoreUpfront,
|
|
||||||
// // gasPrice,
|
|
||||||
// // })
|
|
||||||
// // const txHash = txReceipt.tx
|
|
||||||
// // await checkReceiptStatus(txHash)
|
|
||||||
|
|
||||||
// // await submitOperation(safeAddress, to, valueInWei, data, operation, nonce, txHash, sender, 'execution')
|
|
||||||
|
|
||||||
// // return txHash
|
|
||||||
// // }
|
|
|
@ -1,14 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
|
||||||
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
|
||||||
import { type Operation } from '~/logic/safe/transactions'
|
import { type Operation } from '~/logic/safe/transactions'
|
||||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||||
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
|
||||||
|
|
||||||
export const CALL = 0
|
export const CALL = 0
|
||||||
export const TX_TYPE_EXECUTION = 'execution'
|
export const TX_TYPE_EXECUTION = 'execution'
|
||||||
|
@ -44,13 +38,10 @@ export const getApprovalTransaction = async (
|
||||||
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
|
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
|
||||||
|
|
||||||
return contract.methods.approveHash(txHash)
|
return contract.methods.approveHash(txHash)
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
/* eslint-disable */
|
console.error(`Error while approving transaction: ${err}`)
|
||||||
const executeData = safeInstance.contract.methods.approveHash(txHash).encodeABI()
|
|
||||||
const errMsg = await getErrorMessage(safeInstance.address, 0, executeData, sender)
|
|
||||||
console.log(`Error executing the TX: ${errMsg}`)
|
|
||||||
|
|
||||||
throw error
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,58 +53,16 @@ export const getExecutionTransaction = async (
|
||||||
operation: Operation,
|
operation: Operation,
|
||||||
nonce: string | number,
|
nonce: string | number,
|
||||||
sender: string,
|
sender: string,
|
||||||
signatures?: string,
|
sigs: string,
|
||||||
) => {
|
) => {
|
||||||
let sigs = signatures
|
|
||||||
|
|
||||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
|
||||||
if (!sigs) {
|
|
||||||
sigs = `0x000000000000000000000000${sender.replace(
|
|
||||||
'0x',
|
|
||||||
'',
|
|
||||||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
|
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
|
||||||
|
|
||||||
return contract.methods.execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
return contract.methods.execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
/* eslint-disable */
|
console.error(`Error while creating transaction: ${err}`)
|
||||||
const executeDataUsedSignatures = safeInstance.contract.methods
|
|
||||||
.execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
|
||||||
.encodeABI()
|
|
||||||
const errMsg = await getErrorMessage(safeInstance.address, 0, executeDataUsedSignatures, sender)
|
|
||||||
console.log(`Error executing the TX: ${errMsg}`)
|
|
||||||
|
|
||||||
throw error
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTransaction = async (safeAddress: string, to: string, valueInEth: string, token: Token) => {
|
|
||||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
|
||||||
const web3 = getWeb3()
|
|
||||||
const from = web3.currentProvider.selectedAddress
|
|
||||||
const threshold = await safeInstance.getThreshold()
|
|
||||||
const nonce = (await safeInstance.nonce()).toString()
|
|
||||||
const valueInWei = web3.utils.toWei(valueInEth, 'ether')
|
|
||||||
const isExecution = threshold.toNumber() === 1
|
|
||||||
|
|
||||||
let txData = EMPTY_DATA
|
|
||||||
if (!isEther(token.symbol)) {
|
|
||||||
const StandardToken = await getStandardTokenContract()
|
|
||||||
const sendToken = await StandardToken.at(token.address)
|
|
||||||
|
|
||||||
txData = sendToken.contract.transfer(to, valueInWei).encodeABI()
|
|
||||||
}
|
|
||||||
|
|
||||||
let txHash
|
|
||||||
if (isExecution) {
|
|
||||||
txHash = await executeTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
|
||||||
} else {
|
|
||||||
// txHash = await approveTransaction(safeAddress, to, valueInWei, txData, CALL, nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
return txHash
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,8 +22,7 @@ export const saveSafes = async (safes: Object) => {
|
||||||
try {
|
try {
|
||||||
await saveToStorage(SAFES_KEY, safes)
|
await saveToStorage(SAFES_KEY, safes)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error storing safe info in localstorage', err)
|
||||||
console.log('Error storing safe info in localstorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +31,7 @@ export const setOwners = async (safeAddress: string, owners: List<Owner>) => {
|
||||||
const ownersAsMap = Map(owners.map((owner: Owner) => [owner.address.toLowerCase(), owner.name]))
|
const ownersAsMap = Map(owners.map((owner: Owner) => [owner.address.toLowerCase(), owner.name]))
|
||||||
await saveToStorage(`${OWNERS_KEY}-${safeAddress}`, ownersAsMap)
|
await saveToStorage(`${OWNERS_KEY}-${safeAddress}`, ownersAsMap)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error storing owners in localstorage', err)
|
||||||
console.log('Error storing owners in localstorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,7 @@ export const fetchTokens = () => async (dispatch: ReduxDispatch<GlobalState>) =>
|
||||||
|
|
||||||
dispatch(saveTokens(tokens))
|
dispatch(saveTokens(tokens))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error fetching token list', err)
|
||||||
console.log('Error fetching token list ' + err)
|
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,7 @@ export const saveActiveTokens = async (tokens: Map<string, Token>) => {
|
||||||
try {
|
try {
|
||||||
await saveToStorage(ACTIVE_TOKENS_KEY, tokens.toJS())
|
await saveToStorage(ACTIVE_TOKENS_KEY, tokens.toJS())
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error storing tokens in localstorage', err)
|
||||||
console.log('Error storing tokens in localstorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +37,7 @@ export const removeTokenFromStorage = async (safeAddress: string, token: Token)
|
||||||
const index = data.indexOf(token)
|
const index = data.indexOf(token)
|
||||||
await saveToStorage(CUSTOM_TOKENS_KEY, data.remove(index))
|
await saveToStorage(CUSTOM_TOKENS_KEY, data.remove(index))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error removing token in localstorage', err)
|
||||||
console.log('Error removing token in localstorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,14 @@ export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
|
||||||
|
|
||||||
if (window.ethereum) {
|
if (window.ethereum) {
|
||||||
web3Provider = window.ethereum
|
web3Provider = window.ethereum
|
||||||
await web3Provider.enable()
|
try {
|
||||||
|
const accounts = await web3Provider.enable()
|
||||||
|
if (!accounts) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error when enabling web3 provider', error)
|
||||||
|
}
|
||||||
} else if (window.web3) {
|
} else if (window.web3) {
|
||||||
web3Provider = window.web3.currentProvider
|
web3Provider = window.web3.currentProvider
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,6 +121,10 @@ export const getAddressFromENS = async (name: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBalanceInEtherOf = async (safeAddress: string) => {
|
export const getBalanceInEtherOf = async (safeAddress: string) => {
|
||||||
|
if (!web3) {
|
||||||
|
return '0'
|
||||||
|
}
|
||||||
|
|
||||||
const funds: String = await web3.eth.getBalance(safeAddress)
|
const funds: String = await web3.eth.getBalance(safeAddress)
|
||||||
|
|
||||||
if (!funds) {
|
if (!funds) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { Dispatch as ReduxDispatch } from 'redux'
|
||||||
import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
|
import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
|
||||||
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
||||||
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
||||||
|
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
||||||
import addProvider from './addProvider'
|
import addProvider from './addProvider'
|
||||||
|
|
||||||
export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: ProviderProps) => {
|
export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: ProviderProps) => {
|
||||||
|
@ -21,30 +22,40 @@ export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: Pr
|
||||||
dispatch(addProvider(walletRecord))
|
dispatch(addProvider(walletRecord))
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUCCESS_MSG = 'Wallet connected sucessfully'
|
const handleProviderNotification = (
|
||||||
const UNLOCK_MSG = 'Unlock your wallet to connect'
|
dispatch: ReduxDispatch<*>,
|
||||||
const WRONG_NETWORK = 'You are connected to wrong network. Please use RINKEBY'
|
provider: ProviderProps,
|
||||||
export const WALLET_ERROR_MSG = 'Error connecting to your wallet'
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
const handleProviderNotification = (openSnackbar: Function, provider: ProviderProps) => {
|
) => {
|
||||||
const { loaded, available, network } = provider
|
const { loaded, available, network } = provider
|
||||||
|
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
openSnackbar(WALLET_ERROR_MSG, 'error')
|
showSnackbar(NOTIFICATIONS.CONNECT_WALLET_ERROR_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ETHEREUM_NETWORK_IDS[network] !== ETHEREUM_NETWORK.RINKEBY) {
|
if (ETHEREUM_NETWORK_IDS[network] !== ETHEREUM_NETWORK.RINKEBY) {
|
||||||
openSnackbar(WRONG_NETWORK, 'error')
|
showSnackbar(NOTIFICATIONS.WRONG_NETWORK_RINKEBY_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
showSnackbar(NOTIFICATIONS.RINKEBY_VERSION_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
const msg = available ? SUCCESS_MSG : UNLOCK_MSG
|
if (available) {
|
||||||
const variant = available ? 'success' : 'warning'
|
// NOTE:
|
||||||
openSnackbar(msg, variant)
|
// if you want to be able to dispatch a `closeSnackbar` action later on,
|
||||||
|
// you SHOULD pass your own `key` in the options. `key` can be any sequence
|
||||||
|
// of number or characters, but it has to be unique to a given snackbar.
|
||||||
|
|
||||||
|
showSnackbar(NOTIFICATIONS.WALLET_CONNECTED_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
|
} else {
|
||||||
|
showSnackbar(NOTIFICATIONS.UNLOCK_WALLET_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (provider: ProviderProps, openSnackbar: Function) => (dispatch: ReduxDispatch<*>) => {
|
export default (provider: ProviderProps, enqueueSnackbar: Function, closeSnackbar: Function) => (
|
||||||
handleProviderNotification(openSnackbar, provider)
|
dispatch: ReduxDispatch<*>,
|
||||||
|
) => {
|
||||||
|
handleProviderNotification(dispatch, provider, enqueueSnackbar, closeSnackbar)
|
||||||
processProviderResponse(dispatch, provider)
|
processProviderResponse(dispatch, provider)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||||
import { makeProvider, type ProviderProps, type Provider } from '~/logic/wallets/store/model/provider'
|
import { makeProvider, type ProviderProps, type Provider } from '~/logic/wallets/store/model/provider'
|
||||||
|
import { NOTIFICATIONS, showSnackbar } from '~/logic/notifications'
|
||||||
import addProvider from './addProvider'
|
import addProvider from './addProvider'
|
||||||
|
|
||||||
export default (openSnackbar: Function) => async (dispatch: ReduxDispatch<*>) => {
|
export default (enqueueSnackbar: Function, closeSnackbar: Function) => async (dispatch: ReduxDispatch<*>) => {
|
||||||
const providerProps: ProviderProps = {
|
const providerProps: ProviderProps = {
|
||||||
name: '',
|
name: '',
|
||||||
available: false,
|
available: false,
|
||||||
|
@ -13,7 +14,7 @@ export default (openSnackbar: Function) => async (dispatch: ReduxDispatch<*>) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider: Provider = makeProvider(providerProps)
|
const provider: Provider = makeProvider(providerProps)
|
||||||
openSnackbar('Wallet disconnected succesfully', 'info')
|
showSnackbar(NOTIFICATIONS.WALLET_DISCONNECTED_MSG, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
dispatch(addProvider(provider))
|
dispatch(addProvider(provider))
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,7 @@ class Load extends React.Component<Props> {
|
||||||
const url = `${SAFELIST_ADDRESS}/${safeAddress}`
|
const url = `${SAFELIST_ADDRESS}/${safeAddress}`
|
||||||
history.push(url)
|
history.push(url)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line
|
console.error('Error while loading the Safe', error)
|
||||||
console.log('Error while loading the Safe' + error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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 { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
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 Link from '~/components/layout/Link'
|
||||||
|
@ -13,11 +13,12 @@ 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 { copyToClipboard } from '~/utils/clipboard'
|
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
|
import { copyToClipboard } from '~/utils/clipboard'
|
||||||
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 { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
import { getEthAsToken } 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 { secondary } from '~/theme/variables'
|
||||||
|
@ -33,6 +34,8 @@ type Props = {
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
tx: Object,
|
tx: Object,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const openIconStyle = {
|
const openIconStyle = {
|
||||||
|
@ -50,110 +53,117 @@ const ReviewCustomTx = ({
|
||||||
ethBalance,
|
ethBalance,
|
||||||
tx,
|
tx,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
}: Props) => (
|
enqueueSnackbar,
|
||||||
<SharedSnackbarConsumer>
|
closeSnackbar,
|
||||||
{({ openSnackbar }) => {
|
}: Props) => {
|
||||||
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
|
||||||
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
|
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
|
||||||
|
|
||||||
createTransaction(safeAddress, txRecipient, txValue, txData, openSnackbar)
|
createTransaction(
|
||||||
onClose()
|
safeAddress,
|
||||||
}
|
txRecipient,
|
||||||
|
txValue,
|
||||||
|
txData,
|
||||||
|
TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row align="center" grow className={classes.heading}>
|
<Row align="center" grow className={classes.heading}>
|
||||||
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
||||||
Send Funds
|
Send Funds
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph className={classes.annotation}>2 of 2</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.closeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Block className={classes.container}>
|
||||||
|
<SafeInfo
|
||||||
|
safeAddress={safeAddress}
|
||||||
|
etherScanLink={etherScanLink}
|
||||||
|
safeName={safeName}
|
||||||
|
ethBalance={ethBalance}
|
||||||
|
/>
|
||||||
|
<Row margin="md">
|
||||||
|
<Col xs={1}>
|
||||||
|
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={11} center="xs" layout="column">
|
||||||
|
<Hairline />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row margin="xs">
|
||||||
|
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
||||||
|
Recipient
|
||||||
|
</Paragraph>
|
||||||
|
</Row>
|
||||||
|
<Row margin="md" align="center">
|
||||||
|
<Col xs={1}>
|
||||||
|
<Identicon address={tx.recipientAddress} diameter={32} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={11} layout="column">
|
||||||
|
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
||||||
|
{tx.recipientAddress}
|
||||||
|
<Link to={etherScanLink} target="_blank">
|
||||||
|
<OpenInNew style={openIconStyle} />
|
||||||
|
</Link>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph className={classes.annotation}>2 of 2</Paragraph>
|
</Col>
|
||||||
<IconButton onClick={onClose} disableRipple>
|
</Row>
|
||||||
<Close className={classes.closeIcon} />
|
<Row margin="xs">
|
||||||
</IconButton>
|
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
||||||
</Row>
|
Value
|
||||||
<Hairline />
|
</Paragraph>
|
||||||
<Block className={classes.container}>
|
</Row>
|
||||||
<SafeInfo
|
<Row margin="md" align="center">
|
||||||
safeAddress={safeAddress}
|
<Img src={getEthAsToken().logoUri} height={28} alt="Ether" onError={setImageToPlaceholder} />
|
||||||
etherScanLink={etherScanLink}
|
<Paragraph size="md" noMargin className={classes.value}>
|
||||||
safeName={safeName}
|
{tx.value || 0}
|
||||||
ethBalance={ethBalance}
|
{' ETH'}
|
||||||
/>
|
</Paragraph>
|
||||||
<Row margin="md">
|
</Row>
|
||||||
<Col xs={1}>
|
<Row margin="xs">
|
||||||
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
||||||
</Col>
|
Data (hex encoded)
|
||||||
<Col xs={11} center="xs" layout="column">
|
</Paragraph>
|
||||||
<Hairline />
|
</Row>
|
||||||
</Col>
|
<Row margin="md" align="center">
|
||||||
|
<Col className={classes.outerData}>
|
||||||
|
<Row size="md" className={classes.data}>
|
||||||
|
{tx.data}
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="xs">
|
</Col>
|
||||||
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
</Row>
|
||||||
Recipient
|
</Block>
|
||||||
</Paragraph>
|
<Hairline />
|
||||||
</Row>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Row margin="md" align="center">
|
<Button minWidth={140} onClick={() => setActiveScreen('sendCustomTx')}>
|
||||||
<Col xs={1}>
|
Back
|
||||||
<Identicon address={tx.recipientAddress} diameter={32} />
|
</Button>
|
||||||
</Col>
|
<Button
|
||||||
<Col xs={11} layout="column">
|
type="submit"
|
||||||
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
onClick={submitTx}
|
||||||
{tx.recipientAddress}
|
variant="contained"
|
||||||
<Link to={etherScanLink} target="_blank">
|
minWidth={140}
|
||||||
<OpenInNew style={openIconStyle} />
|
color="primary"
|
||||||
</Link>
|
data-testid="submit-tx-btn"
|
||||||
</Paragraph>
|
className={classes.submitButton}
|
||||||
</Col>
|
>
|
||||||
</Row>
|
Submit
|
||||||
<Row margin="xs">
|
</Button>
|
||||||
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
</Row>
|
||||||
Value
|
</>
|
||||||
</Paragraph>
|
)
|
||||||
</Row>
|
}
|
||||||
<Row margin="md" align="center">
|
|
||||||
<Img src={getEthAsToken().logoUri} height={28} alt="Ether" onError={setImageToPlaceholder} />
|
|
||||||
<Paragraph size="md" noMargin className={classes.value}>
|
|
||||||
{tx.value || 0}
|
|
||||||
{' ETH'}
|
|
||||||
</Paragraph>
|
|
||||||
</Row>
|
|
||||||
<Row margin="xs">
|
|
||||||
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
|
||||||
Data (hex encoded)
|
|
||||||
</Paragraph>
|
|
||||||
</Row>
|
|
||||||
<Row margin="md" align="center">
|
|
||||||
<Col className={classes.outerData}>
|
|
||||||
<Row size="md" className={classes.data}>
|
|
||||||
{tx.data}
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Block>
|
|
||||||
<Hairline />
|
|
||||||
<Row align="center" className={classes.buttonRow}>
|
|
||||||
<Button minWidth={140} onClick={() => setActiveScreen('sendCustomTx')}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
onClick={submitTx}
|
|
||||||
variant="contained"
|
|
||||||
minWidth={140}
|
|
||||||
color="primary"
|
|
||||||
data-testid="submit-tx-btn"
|
|
||||||
className={classes.submitButton}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(ReviewCustomTx)
|
|
||||||
|
export default withStyles(styles)(withSnackbar(ReviewCustomTx))
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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 { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
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 Link from '~/components/layout/Link'
|
||||||
|
@ -20,6 +20,7 @@ import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
import { secondary } from '~/theme/variables'
|
import { secondary } from '~/theme/variables'
|
||||||
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
|
@ -35,6 +36,8 @@ type Props = {
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
tx: Object,
|
tx: Object,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const openIconStyle = {
|
const openIconStyle = {
|
||||||
|
@ -52,111 +55,117 @@ const ReviewTx = ({
|
||||||
ethBalance,
|
ethBalance,
|
||||||
tx,
|
tx,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
}: Props) => (
|
enqueueSnackbar,
|
||||||
<SharedSnackbarConsumer>
|
closeSnackbar,
|
||||||
{({ openSnackbar }) => {
|
}: Props) => {
|
||||||
const submitTx = async () => {
|
const submitTx = async () => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const isSendingETH = isEther(tx.token.symbol)
|
const isSendingETH = isEther(tx.token.symbol)
|
||||||
const txRecipient = isSendingETH ? tx.recipientAddress : tx.token.address
|
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 StandardToken = await getStandardTokenContract()
|
const StandardToken = await getStandardTokenContract()
|
||||||
const tokenInstance = await StandardToken.at(tx.token.address)
|
const tokenInstance = await StandardToken.at(tx.token.address)
|
||||||
|
|
||||||
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
|
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
|
||||||
// txAmount should be 0 if we send tokens
|
// txAmount should be 0 if we send tokens
|
||||||
// the real value is encoded in txData and will be used by the contract
|
// the real value is encoded in txData and will be used by the contract
|
||||||
// if txAmount > 0 it would send ETH from the safe
|
// if txAmount > 0 it would send ETH from the safe
|
||||||
txAmount = 0
|
txAmount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
createTransaction(safeAddress, txRecipient, txAmount, txData, openSnackbar)
|
createTransaction(
|
||||||
onClose()
|
safeAddress,
|
||||||
}
|
txRecipient,
|
||||||
|
txAmount,
|
||||||
|
txData,
|
||||||
|
TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row align="center" grow className={classes.heading}>
|
<Row align="center" grow className={classes.heading}>
|
||||||
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
||||||
Send Funds
|
Send Funds
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph className={classes.annotation}>2 of 2</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.closeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Block className={classes.container}>
|
||||||
|
<SafeInfo
|
||||||
|
safeAddress={safeAddress}
|
||||||
|
etherScanLink={etherScanLink}
|
||||||
|
safeName={safeName}
|
||||||
|
ethBalance={ethBalance}
|
||||||
|
/>
|
||||||
|
<Row margin="md">
|
||||||
|
<Col xs={1}>
|
||||||
|
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={11} center="xs" layout="column">
|
||||||
|
<Hairline />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row margin="xs">
|
||||||
|
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
||||||
|
Recipient
|
||||||
|
</Paragraph>
|
||||||
|
</Row>
|
||||||
|
<Row margin="md" align="center">
|
||||||
|
<Col xs={1}>
|
||||||
|
<Identicon address={tx.recipientAddress} diameter={32} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={11} layout="column">
|
||||||
|
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
||||||
|
{tx.recipientAddress}
|
||||||
|
<Link to={etherScanLink} target="_blank">
|
||||||
|
<OpenInNew style={openIconStyle} />
|
||||||
|
</Link>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph className={classes.annotation}>2 of 2</Paragraph>
|
</Col>
|
||||||
<IconButton onClick={onClose} disableRipple>
|
</Row>
|
||||||
<Close className={classes.closeIcon} />
|
<Row margin="xs">
|
||||||
</IconButton>
|
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
||||||
</Row>
|
Amount
|
||||||
<Hairline />
|
</Paragraph>
|
||||||
<Block className={classes.container}>
|
</Row>
|
||||||
<SafeInfo
|
<Row margin="md" align="center">
|
||||||
safeAddress={safeAddress}
|
<Img src={tx.token.logoUri} height={28} alt={tx.token.name} onError={setImageToPlaceholder} />
|
||||||
etherScanLink={etherScanLink}
|
<Paragraph size="md" noMargin className={classes.amount}>
|
||||||
safeName={safeName}
|
{tx.amount}
|
||||||
ethBalance={ethBalance}
|
{' '}
|
||||||
/>
|
{tx.token.symbol}
|
||||||
<Row margin="md">
|
</Paragraph>
|
||||||
<Col xs={1}>
|
</Row>
|
||||||
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
</Block>
|
||||||
</Col>
|
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
||||||
<Col xs={11} center="xs" layout="column">
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Hairline />
|
<Button minWidth={140} onClick={() => setActiveScreen('sendFunds')}>
|
||||||
</Col>
|
Back
|
||||||
</Row>
|
</Button>
|
||||||
<Row margin="xs">
|
<Button
|
||||||
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
type="submit"
|
||||||
Recipient
|
onClick={submitTx}
|
||||||
</Paragraph>
|
variant="contained"
|
||||||
</Row>
|
minWidth={140}
|
||||||
<Row margin="md" align="center">
|
color="primary"
|
||||||
<Col xs={1}>
|
data-testid="submit-tx-btn"
|
||||||
<Identicon address={tx.recipientAddress} diameter={32} />
|
className={classes.submitButton}
|
||||||
</Col>
|
>
|
||||||
<Col xs={11} layout="column">
|
Submit
|
||||||
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
</Button>
|
||||||
{tx.recipientAddress}
|
</Row>
|
||||||
<Link to={etherScanLink} target="_blank">
|
</>
|
||||||
<OpenInNew style={openIconStyle} />
|
)
|
||||||
</Link>
|
}
|
||||||
</Paragraph>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row margin="xs">
|
|
||||||
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
|
||||||
Amount
|
|
||||||
</Paragraph>
|
|
||||||
</Row>
|
|
||||||
<Row margin="md" align="center">
|
|
||||||
<Img src={tx.token.logoUri} height={28} alt={tx.token.name} onError={setImageToPlaceholder} />
|
|
||||||
<Paragraph size="md" noMargin className={classes.amount}>
|
|
||||||
{tx.amount}
|
|
||||||
{' '}
|
|
||||||
{tx.token.symbol}
|
|
||||||
</Paragraph>
|
|
||||||
</Row>
|
|
||||||
</Block>
|
|
||||||
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
|
||||||
<Row align="center" className={classes.buttonRow}>
|
|
||||||
<Button minWidth={140} onClick={() => setActiveScreen('sendFunds')}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
onClick={submitTx}
|
|
||||||
variant="contained"
|
|
||||||
minWidth={140}
|
|
||||||
color="primary"
|
|
||||||
data-testid="submit-tx-btn"
|
|
||||||
className={classes.submitButton}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(ReviewTx)
|
export default withStyles(styles)(withSnackbar(ReviewTx))
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import { withSnackbar } from 'notistack'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import Heading from '~/components/layout/Heading'
|
import Heading from '~/components/layout/Heading'
|
||||||
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
|
||||||
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
|
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
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 { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
|
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
|
||||||
|
@ -22,17 +24,20 @@ type Props = {
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
safeName: string,
|
safeName: string,
|
||||||
updateSafe: Function,
|
updateSafe: Function,
|
||||||
openSnackbar: Function,
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChangeSafeName = (props: Props) => {
|
const ChangeSafeName = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
classes, safeAddress, safeName, updateSafe, openSnackbar,
|
classes, safeAddress, safeName, updateSafe, enqueueSnackbar, closeSnackbar,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
updateSafe({ address: safeAddress, name: values.safeName })
|
updateSafe({ address: safeAddress, name: values.safeName })
|
||||||
openSnackbar('Safe name changed', 'success')
|
|
||||||
|
const notification = getNofiticationsFromTxType(TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX)
|
||||||
|
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -80,10 +85,4 @@ const ChangeSafeName = (props: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const withSnackbar = (props) => (
|
export default withStyles(styles)(withSnackbar(ChangeSafeName))
|
||||||
<SharedSnackbarConsumer>
|
|
||||||
{({ openSnackbar }) => <ChangeSafeName {...props} openSnackbar={openSnackbar} />}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar)
|
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
import React, { useState, useEffect } 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 { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
import { withSnackbar } from 'notistack'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import { type Owner } from '~/routes/safe/store/models/owner'
|
import { type Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import OwnerForm from './screens/OwnerForm'
|
import OwnerForm from './screens/OwnerForm'
|
||||||
import ThresholdForm from './screens/ThresholdForm'
|
import ThresholdForm from './screens/ThresholdForm'
|
||||||
import ReviewAddOwner from './screens/Review'
|
import ReviewAddOwner from './screens/Review'
|
||||||
|
@ -29,6 +30,8 @@ type Props = {
|
||||||
network: string,
|
network: string,
|
||||||
addSafeOwner: Function,
|
addSafeOwner: Function,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
type ActiveScreen = 'selectOwner' | 'selectThreshold' | 'reviewAddOwner'
|
type ActiveScreen = 'selectOwner' | 'selectThreshold' | 'reviewAddOwner'
|
||||||
|
|
||||||
|
@ -36,14 +39,23 @@ export const sendAddOwner = async (
|
||||||
values: Object,
|
values: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
ownersOld: List<Owner>,
|
ownersOld: List<Owner>,
|
||||||
openSnackbar: Function,
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
addSafeOwner: Function,
|
addSafeOwner: Function,
|
||||||
) => {
|
) => {
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const txData = gnosisSafe.contract.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI()
|
const txData = gnosisSafe.contract.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI()
|
||||||
|
|
||||||
const txHash = await createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar)
|
const txHash = await createTransaction(
|
||||||
|
safeAddress,
|
||||||
|
safeAddress,
|
||||||
|
0,
|
||||||
|
txData,
|
||||||
|
TX_NOTIFICATION_TYPES.OWNER_CHANGE_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
|
||||||
if (txHash) {
|
if (txHash) {
|
||||||
addSafeOwner({ safeAddress, ownerName: values.ownerName, ownerAddress: values.ownerAddress })
|
addSafeOwner({ safeAddress, ownerName: values.ownerName, ownerAddress: values.ownerAddress })
|
||||||
|
@ -61,6 +73,8 @@ const AddOwner = ({
|
||||||
network,
|
network,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
addSafeOwner,
|
addSafeOwner,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [activeScreen, setActiveScreen] = useState<ActiveScreen>('selectOwner')
|
const [activeScreen, setActiveScreen] = useState<ActiveScreen>('selectOwner')
|
||||||
const [values, setValues] = useState<Object>({})
|
const [values, setValues] = useState<Object>({})
|
||||||
|
@ -98,59 +112,50 @@ const AddOwner = ({
|
||||||
setActiveScreen('reviewAddOwner')
|
setActiveScreen('reviewAddOwner')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const onAddOwner = async () => {
|
||||||
<>
|
onClose()
|
||||||
<SharedSnackbarConsumer>
|
try {
|
||||||
{({ openSnackbar }) => {
|
sendAddOwner(values, safeAddress, owners, enqueueSnackbar, closeSnackbar, createTransaction, addSafeOwner)
|
||||||
const onAddOwner = async () => {
|
} catch (error) {
|
||||||
onClose()
|
console.error('Error while removing an owner', error)
|
||||||
try {
|
}
|
||||||
sendAddOwner(values, safeAddress, owners, openSnackbar, createTransaction, addSafeOwner)
|
}
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log('Error while removing an owner ' + error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Add owner to Safe"
|
title="Add owner to Safe"
|
||||||
description="Add owner to Safe"
|
description="Add owner to Safe"
|
||||||
handleClose={onClose}
|
handleClose={onClose}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
paperClassName={classes.biggerModalWindow}
|
paperClassName={classes.biggerModalWindow}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{activeScreen === 'selectOwner' && (
|
{activeScreen === 'selectOwner' && (
|
||||||
<OwnerForm onClose={onClose} onSubmit={ownerSubmitted} owners={owners} />
|
<OwnerForm onClose={onClose} onSubmit={ownerSubmitted} owners={owners} />
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'selectThreshold' && (
|
{activeScreen === 'selectThreshold' && (
|
||||||
<ThresholdForm
|
<ThresholdForm
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={thresholdSubmitted}
|
onSubmit={thresholdSubmitted}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewAddOwner' && (
|
{activeScreen === 'reviewAddOwner' && (
|
||||||
<ReviewAddOwner
|
<ReviewAddOwner
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
network={network}
|
network={network}
|
||||||
values={values}
|
values={values}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={onAddOwner}
|
onSubmit={onAddOwner}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(AddOwner)
|
export default withStyles(styles)(withSnackbar(AddOwner))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
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 OpenInNew from '@material-ui/icons/OpenInNew'
|
||||||
|
@ -14,8 +15,10 @@ import Field from '~/components/forms/Field'
|
||||||
import TextField from '~/components/forms/TextField'
|
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 { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
|
||||||
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
|
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
|
||||||
|
import { getNofiticationsFromTxType, showSnackbar } from '~/logic/notifications'
|
||||||
|
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 { secondary } from '~/theme/variables'
|
||||||
|
@ -37,6 +40,8 @@ type Props = {
|
||||||
network: string,
|
network: string,
|
||||||
selectedOwnerName: string,
|
selectedOwnerName: string,
|
||||||
editSafeOwner: Function,
|
editSafeOwner: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditOwnerComponent = ({
|
const EditOwnerComponent = ({
|
||||||
|
@ -48,9 +53,15 @@ const EditOwnerComponent = ({
|
||||||
selectedOwnerName,
|
selectedOwnerName,
|
||||||
editSafeOwner,
|
editSafeOwner,
|
||||||
network,
|
network,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
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)
|
||||||
|
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +127,6 @@ const EditOwnerComponent = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditOwnerModal = withStyles(styles)(EditOwnerComponent)
|
const EditOwnerModal = withStyles(styles)(withSnackbar(EditOwnerComponent))
|
||||||
|
|
||||||
export default EditOwnerModal
|
export default EditOwnerModal
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
import React, { useState, useEffect } 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 { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
import { withSnackbar } from 'notistack'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import { type Owner } from '~/routes/safe/store/models/owner'
|
import { type Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import CheckOwner from './screens/CheckOwner'
|
import CheckOwner from './screens/CheckOwner'
|
||||||
import ThresholdForm from './screens/ThresholdForm'
|
import ThresholdForm from './screens/ThresholdForm'
|
||||||
import ReviewRemoveOwner from './screens/Review'
|
import ReviewRemoveOwner from './screens/Review'
|
||||||
|
@ -31,7 +32,10 @@ type Props = {
|
||||||
network: string,
|
network: string,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
removeSafeOwner: Function,
|
removeSafeOwner: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActiveScreen = 'checkOwner' | 'selectThreshold' | 'reviewRemoveOwner'
|
type ActiveScreen = 'checkOwner' | 'selectThreshold' | 'reviewRemoveOwner'
|
||||||
|
|
||||||
export const sendRemoveOwner = async (
|
export const sendRemoveOwner = async (
|
||||||
|
@ -40,7 +44,8 @@ export const sendRemoveOwner = async (
|
||||||
ownerAddressToRemove: string,
|
ownerAddressToRemove: string,
|
||||||
ownerNameToRemove: string,
|
ownerNameToRemove: string,
|
||||||
ownersOld: List<Owner>,
|
ownersOld: List<Owner>,
|
||||||
openSnackbar: Function,
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
removeSafeOwner: Function,
|
removeSafeOwner: Function,
|
||||||
) => {
|
) => {
|
||||||
|
@ -54,7 +59,15 @@ export const sendRemoveOwner = async (
|
||||||
.removeOwner(prevAddress, ownerAddressToRemove, values.threshold)
|
.removeOwner(prevAddress, ownerAddressToRemove, values.threshold)
|
||||||
.encodeABI()
|
.encodeABI()
|
||||||
|
|
||||||
const txHash = await createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar)
|
const txHash = await createTransaction(
|
||||||
|
safeAddress,
|
||||||
|
safeAddress,
|
||||||
|
0,
|
||||||
|
txData,
|
||||||
|
TX_NOTIFICATION_TYPES.OWNER_CHANGE_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
|
||||||
if (txHash) {
|
if (txHash) {
|
||||||
removeSafeOwner({ safeAddress, ownerAddress: ownerAddressToRemove })
|
removeSafeOwner({ safeAddress, ownerAddress: ownerAddressToRemove })
|
||||||
|
@ -74,6 +87,8 @@ const RemoveOwner = ({
|
||||||
network,
|
network,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
removeSafeOwner,
|
removeSafeOwner,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [activeScreen, setActiveScreen] = useState<ActiveScreen>('checkOwner')
|
const [activeScreen, setActiveScreen] = useState<ActiveScreen>('checkOwner')
|
||||||
const [values, setValues] = useState<Object>({})
|
const [values, setValues] = useState<Object>({})
|
||||||
|
@ -104,71 +119,64 @@ const RemoveOwner = ({
|
||||||
setActiveScreen('reviewRemoveOwner')
|
setActiveScreen('reviewRemoveOwner')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const onRemoveOwner = () => {
|
||||||
<>
|
onClose()
|
||||||
<SharedSnackbarConsumer>
|
sendRemoveOwner(
|
||||||
{({ openSnackbar }) => {
|
values,
|
||||||
const onRemoveOwner = () => {
|
safeAddress,
|
||||||
onClose()
|
ownerAddress,
|
||||||
sendRemoveOwner(
|
ownerName,
|
||||||
values,
|
owners,
|
||||||
safeAddress,
|
enqueueSnackbar,
|
||||||
ownerAddress,
|
closeSnackbar,
|
||||||
ownerName,
|
createTransaction,
|
||||||
owners,
|
removeSafeOwner,
|
||||||
openSnackbar,
|
)
|
||||||
createTransaction,
|
}
|
||||||
removeSafeOwner,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Remove owner from Safe"
|
title="Remove owner from Safe"
|
||||||
description="Remove owner from Safe"
|
description="Remove owner from Safe"
|
||||||
handleClose={onClose}
|
handleClose={onClose}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
paperClassName={classes.biggerModalWindow}
|
paperClassName={classes.biggerModalWindow}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{activeScreen === 'checkOwner' && (
|
{activeScreen === 'checkOwner' && (
|
||||||
<CheckOwner
|
<CheckOwner
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
ownerAddress={ownerAddress}
|
ownerAddress={ownerAddress}
|
||||||
ownerName={ownerName}
|
ownerName={ownerName}
|
||||||
network={network}
|
network={network}
|
||||||
onSubmit={ownerSubmitted}
|
onSubmit={ownerSubmitted}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'selectThreshold' && (
|
{activeScreen === 'selectThreshold' && (
|
||||||
<ThresholdForm
|
<ThresholdForm
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={thresholdSubmitted}
|
onSubmit={thresholdSubmitted}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewRemoveOwner' && (
|
{activeScreen === 'reviewRemoveOwner' && (
|
||||||
<ReviewRemoveOwner
|
<ReviewRemoveOwner
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
network={network}
|
network={network}
|
||||||
values={values}
|
values={values}
|
||||||
ownerAddress={ownerAddress}
|
ownerAddress={ownerAddress}
|
||||||
ownerName={ownerName}
|
ownerName={ownerName}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={onRemoveOwner}
|
onSubmit={onRemoveOwner}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(RemoveOwner)
|
export default withStyles(styles)(withSnackbar(RemoveOwner))
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
import React, { useState, useEffect } 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 { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
import { withSnackbar } from 'notistack'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
|
||||||
import OwnerForm from './screens/OwnerForm'
|
import OwnerForm from './screens/OwnerForm'
|
||||||
import ReviewReplaceOwner from './screens/Review'
|
import ReviewReplaceOwner from './screens/Review'
|
||||||
|
@ -29,6 +30,8 @@ type Props = {
|
||||||
threshold: string,
|
threshold: string,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
replaceSafeOwner: Function,
|
replaceSafeOwner: Function,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
type ActiveScreen = 'checkOwner' | 'reviewReplaceOwner'
|
type ActiveScreen = 'checkOwner' | 'reviewReplaceOwner'
|
||||||
|
|
||||||
|
@ -36,7 +39,8 @@ export const sendReplaceOwner = async (
|
||||||
values: Object,
|
values: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
ownerAddressToRemove: string,
|
ownerAddressToRemove: string,
|
||||||
openSnackbar: Function,
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
replaceSafeOwner: Function,
|
replaceSafeOwner: Function,
|
||||||
) => {
|
) => {
|
||||||
|
@ -50,7 +54,15 @@ export const sendReplaceOwner = async (
|
||||||
.swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress)
|
.swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress)
|
||||||
.encodeABI()
|
.encodeABI()
|
||||||
|
|
||||||
const txHash = await createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar)
|
const txHash = await createTransaction(
|
||||||
|
safeAddress,
|
||||||
|
safeAddress,
|
||||||
|
0,
|
||||||
|
txData,
|
||||||
|
TX_NOTIFICATION_TYPES.OWNER_CHANGE_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
|
||||||
if (txHash) {
|
if (txHash) {
|
||||||
replaceSafeOwner({
|
replaceSafeOwner({
|
||||||
|
@ -75,6 +87,8 @@ const ReplaceOwner = ({
|
||||||
threshold,
|
threshold,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
replaceSafeOwner,
|
replaceSafeOwner,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [activeScreen, setActiveScreen] = useState<ActiveScreen>('checkOwner')
|
const [activeScreen, setActiveScreen] = useState<ActiveScreen>('checkOwner')
|
||||||
const [values, setValues] = useState<Object>({})
|
const [values, setValues] = useState<Object>({})
|
||||||
|
@ -96,67 +110,59 @@ const ReplaceOwner = ({
|
||||||
setActiveScreen('reviewReplaceOwner')
|
setActiveScreen('reviewReplaceOwner')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const onReplaceOwner = () => {
|
||||||
<>
|
onClose()
|
||||||
<SharedSnackbarConsumer>
|
try {
|
||||||
{({ openSnackbar }) => {
|
sendReplaceOwner(
|
||||||
const onReplaceOwner = () => {
|
values,
|
||||||
onClose()
|
safeAddress,
|
||||||
try {
|
ownerAddress,
|
||||||
sendReplaceOwner(
|
enqueueSnackbar,
|
||||||
values,
|
closeSnackbar,
|
||||||
safeAddress,
|
createTransaction,
|
||||||
ownerAddress,
|
replaceSafeOwner,
|
||||||
openSnackbar,
|
)
|
||||||
createTransaction,
|
} catch (error) {
|
||||||
replaceSafeOwner,
|
console.error('Error while removing an owner', error)
|
||||||
)
|
}
|
||||||
} catch (error) {
|
}
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log('Error while removing an owner ' + error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Replace owner from Safe"
|
title="Replace owner from Safe"
|
||||||
description="Replace owner from Safe"
|
description="Replace owner from Safe"
|
||||||
handleClose={onClose}
|
handleClose={onClose}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
paperClassName={classes.biggerModalWindow}
|
paperClassName={classes.biggerModalWindow}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{activeScreen === 'checkOwner' && (
|
{activeScreen === 'checkOwner' && (
|
||||||
<OwnerForm
|
<OwnerForm
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
ownerAddress={ownerAddress}
|
ownerAddress={ownerAddress}
|
||||||
ownerName={ownerName}
|
ownerName={ownerName}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
network={network}
|
network={network}
|
||||||
onSubmit={ownerSubmitted}
|
onSubmit={ownerSubmitted}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'reviewReplaceOwner' && (
|
{activeScreen === 'reviewReplaceOwner' && (
|
||||||
<ReviewReplaceOwner
|
<ReviewReplaceOwner
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
network={network}
|
network={network}
|
||||||
values={values}
|
values={values}
|
||||||
ownerAddress={ownerAddress}
|
ownerAddress={ownerAddress}
|
||||||
ownerName={ownerName}
|
ownerName={ownerName}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={onReplaceOwner}
|
onSubmit={onReplaceOwner}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(ReplaceOwner)
|
export default withStyles(styles)(withSnackbar(ReplaceOwner))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
import { withSnackbar } from 'notistack'
|
||||||
import Heading from '~/components/layout/Heading'
|
import Heading from '~/components/layout/Heading'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Bold from '~/components/layout/Bold'
|
import Bold from '~/components/layout/Bold'
|
||||||
|
@ -10,10 +10,11 @@ import Block from '~/components/layout/Block'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import ChangeThreshold from './ChangeThreshold'
|
import ChangeThreshold from './ChangeThreshold'
|
||||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { styles } from './style'
|
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
owners: List<Owner>,
|
owners: List<Owner>,
|
||||||
|
@ -22,10 +23,19 @@ type Props = {
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
granted: boolean,
|
granted: boolean,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThresholdSettings = ({
|
const ThresholdSettings = ({
|
||||||
owners, threshold, classes, createTransaction, safeAddress, granted,
|
owners,
|
||||||
|
threshold,
|
||||||
|
classes,
|
||||||
|
createTransaction,
|
||||||
|
safeAddress,
|
||||||
|
granted,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isModalOpen, setModalOpen] = useState(false)
|
const [isModalOpen, setModalOpen] = useState(false)
|
||||||
|
|
||||||
|
@ -33,66 +43,66 @@ const ThresholdSettings = ({
|
||||||
setModalOpen((prevOpen) => !prevOpen)
|
setModalOpen((prevOpen) => !prevOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onChangeThreshold = async (newThreshold) => {
|
||||||
|
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
|
const txData = safeInstance.contract.methods.changeThreshold(newThreshold).encodeABI()
|
||||||
|
|
||||||
|
createTransaction(
|
||||||
|
safeAddress,
|
||||||
|
safeAddress,
|
||||||
|
0,
|
||||||
|
txData,
|
||||||
|
TX_NOTIFICATION_TYPES.THRESHOLD_CHANGE_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SharedSnackbarConsumer>
|
<Block className={classes.container}>
|
||||||
{({ openSnackbar }) => {
|
<Heading tag="h2">Required confirmations</Heading>
|
||||||
const onChangeThreshold = async (newThreshold) => {
|
<Paragraph>
|
||||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
Any transaction requires the confirmation of:
|
||||||
const txData = safeInstance.contract.methods.changeThreshold(newThreshold).encodeABI()
|
</Paragraph>
|
||||||
|
<Paragraph size="lg" className={classes.ownersText}>
|
||||||
createTransaction(safeAddress, safeAddress, 0, txData, openSnackbar)
|
<Bold>{threshold}</Bold>
|
||||||
}
|
{' '}
|
||||||
|
out of
|
||||||
return (
|
{' '}
|
||||||
<>
|
<Bold>{owners.size}</Bold>
|
||||||
<Block className={classes.container}>
|
{' '}
|
||||||
<Heading tag="h2">Required confirmations</Heading>
|
owners
|
||||||
<Paragraph>
|
</Paragraph>
|
||||||
Any transaction requires the confirmation of:
|
{owners.size > 1 && granted && (
|
||||||
</Paragraph>
|
<Row className={classes.buttonRow}>
|
||||||
<Paragraph size="lg" className={classes.ownersText}>
|
<Button
|
||||||
<Bold>{threshold}</Bold>
|
color="primary"
|
||||||
{' '}
|
minWidth={120}
|
||||||
out of
|
className={classes.modifyBtn}
|
||||||
{' '}
|
onClick={toggleModal}
|
||||||
<Bold>{owners.size}</Bold>
|
variant="contained"
|
||||||
{' '}
|
>
|
||||||
owners
|
Modify
|
||||||
</Paragraph>
|
</Button>
|
||||||
{owners.size > 1 && granted && (
|
</Row>
|
||||||
<Row className={classes.buttonRow}>
|
)}
|
||||||
<Button
|
</Block>
|
||||||
color="primary"
|
<Modal
|
||||||
minWidth={120}
|
title="Change Required Confirmations"
|
||||||
className={classes.modifyBtn}
|
description="Change Required Confirmations Form"
|
||||||
onClick={toggleModal}
|
handleClose={toggleModal}
|
||||||
variant="contained"
|
open={isModalOpen}
|
||||||
>
|
>
|
||||||
Modify
|
<ChangeThreshold
|
||||||
</Button>
|
onClose={toggleModal}
|
||||||
</Row>
|
owners={owners}
|
||||||
)}
|
threshold={threshold}
|
||||||
</Block>
|
onChangeThreshold={onChangeThreshold}
|
||||||
<Modal
|
/>
|
||||||
title="Change Required Confirmations"
|
</Modal>
|
||||||
description="Change Required Confirmations Form"
|
|
||||||
handleClose={toggleModal}
|
|
||||||
open={isModalOpen}
|
|
||||||
>
|
|
||||||
<ChangeThreshold
|
|
||||||
onClose={toggleModal}
|
|
||||||
owners={owners}
|
|
||||||
threshold={threshold}
|
|
||||||
onChangeThreshold={onChangeThreshold}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(ThresholdSettings)
|
export default withStyles(styles)(withSnackbar(ThresholdSettings))
|
||||||
|
|
|
@ -5,7 +5,7 @@ import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||||
import Checkbox from '@material-ui/core/Checkbox'
|
import Checkbox from '@material-ui/core/Checkbox'
|
||||||
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
import { withSnackbar } from 'notistack'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
|
@ -13,6 +13,7 @@ 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 { 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'
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ type Props = {
|
||||||
threshold: number,
|
threshold: number,
|
||||||
thresholdReached: boolean,
|
thresholdReached: boolean,
|
||||||
userAddress: string,
|
userAddress: string,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const getModalTitleAndDescription = (thresholdReached: boolean) => {
|
const getModalTitleAndDescription = (thresholdReached: boolean) => {
|
||||||
|
@ -55,6 +58,8 @@ const ApproveTxModal = ({
|
||||||
threshold,
|
threshold,
|
||||||
thresholdReached,
|
thresholdReached,
|
||||||
userAddress,
|
userAddress,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [approveAndExecute, setApproveAndExecute] = useState<boolean>(false)
|
const [approveAndExecute, setApproveAndExecute] = useState<boolean>(false)
|
||||||
const { title, description } = getModalTitleAndDescription(thresholdReached)
|
const { title, description } = getModalTitleAndDescription(thresholdReached)
|
||||||
|
@ -62,68 +67,70 @@ const ApproveTxModal = ({
|
||||||
|
|
||||||
const handleExecuteCheckbox = () => setApproveAndExecute((prevApproveAndExecute) => !prevApproveAndExecute)
|
const handleExecuteCheckbox = () => setApproveAndExecute((prevApproveAndExecute) => !prevApproveAndExecute)
|
||||||
|
|
||||||
return (
|
const approveTx = () => {
|
||||||
<SharedSnackbarConsumer>
|
processTransaction(
|
||||||
{({ openSnackbar }) => {
|
safeAddress,
|
||||||
const approveTx = () => {
|
tx,
|
||||||
processTransaction(safeAddress, tx, openSnackbar, userAddress, approveAndExecute)
|
userAddress,
|
||||||
onClose()
|
TX_NOTIFICATION_TYPES.CONFIRMATION_TX,
|
||||||
}
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
approveAndExecute,
|
||||||
|
)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={title} description={description} handleClose={onClose} open={isOpen}>
|
<Modal title={title} description={description} handleClose={onClose} open={isOpen}>
|
||||||
<Row align="center" grow className={classes.heading}>
|
<Row align="center" grow className={classes.heading}>
|
||||||
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
||||||
{title}
|
{title}
|
||||||
|
</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.closeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Block className={classes.container}>
|
||||||
|
<Row>
|
||||||
|
<Paragraph>{description}</Paragraph>
|
||||||
|
<Paragraph size="sm" color="medium">
|
||||||
|
Transaction nonce:
|
||||||
|
<br />
|
||||||
|
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
|
||||||
|
</Paragraph>
|
||||||
|
{!thresholdReached && oneConfirmationLeft && (
|
||||||
|
<>
|
||||||
|
<Paragraph color="error">
|
||||||
|
Approving transaction does not execute it immediately. If you want to approve and execute the
|
||||||
|
transaction right away, click on checkbox below.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<IconButton onClick={onClose} disableRipple>
|
<FormControlLabel
|
||||||
<Close className={classes.closeIcon} />
|
control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />}
|
||||||
</IconButton>
|
label="Execute transaction"
|
||||||
</Row>
|
/>
|
||||||
<Hairline />
|
</>
|
||||||
<Block className={classes.container}>
|
)}
|
||||||
<Row>
|
</Row>
|
||||||
<Paragraph>{description}</Paragraph>
|
</Block>
|
||||||
<Paragraph size="sm" color="medium">
|
<Row align="center" className={classes.buttonRow}>
|
||||||
Transaction nonce:
|
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
||||||
<br />
|
Exit
|
||||||
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
|
</Button>
|
||||||
</Paragraph>
|
<Button
|
||||||
{!thresholdReached && oneConfirmationLeft && (
|
type="submit"
|
||||||
<>
|
variant="contained"
|
||||||
<Paragraph color="error">
|
minWidth={214}
|
||||||
Approving transaction does not execute it immediately. If you want to approve and execute the
|
minHeight={42}
|
||||||
transaction right away, click on checkbox below.
|
color="primary"
|
||||||
</Paragraph>
|
onClick={approveTx}
|
||||||
<FormControlLabel
|
testId={APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID}
|
||||||
control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />}
|
>
|
||||||
label="Execute transaction"
|
{title}
|
||||||
/>
|
</Button>
|
||||||
</>
|
</Row>
|
||||||
)}
|
</Modal>
|
||||||
</Row>
|
|
||||||
</Block>
|
|
||||||
<Row align="center" className={classes.buttonRow}>
|
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
|
||||||
Exit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
minWidth={214}
|
|
||||||
minHeight={42}
|
|
||||||
color="primary"
|
|
||||||
onClick={approveTx}
|
|
||||||
testId={APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(ApproveTxModal)
|
export default withStyles(styles)(withSnackbar(ApproveTxModal))
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React 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'
|
||||||
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
|
import { withSnackbar } from 'notistack'
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
|
@ -13,6 +13,7 @@ import Block from '~/components/layout/Block'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
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 { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -22,67 +23,80 @@ type Props = {
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CancelTxModal = ({
|
const CancelTxModal = ({
|
||||||
onClose, isOpen, classes, createTransaction, tx, safeAddress,
|
onClose,
|
||||||
}: Props) => (
|
isOpen,
|
||||||
<SharedSnackbarConsumer>
|
classes,
|
||||||
{({ openSnackbar }) => {
|
createTransaction,
|
||||||
const sendReplacementTransaction = () => {
|
tx,
|
||||||
createTransaction(safeAddress, safeAddress, 0, EMPTY_DATA, openSnackbar)
|
safeAddress,
|
||||||
onClose()
|
enqueueSnackbar,
|
||||||
}
|
closeSnackbar,
|
||||||
|
}: Props) => {
|
||||||
|
const sendReplacementTransaction = () => {
|
||||||
|
createTransaction(
|
||||||
|
safeAddress,
|
||||||
|
safeAddress,
|
||||||
|
0,
|
||||||
|
EMPTY_DATA,
|
||||||
|
TX_NOTIFICATION_TYPES.CANCELLATION_TX,
|
||||||
|
enqueueSnackbar,
|
||||||
|
closeSnackbar,
|
||||||
|
)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Cancel Transaction"
|
title="Cancel Transaction"
|
||||||
description="Cancel Transaction"
|
description="Cancel Transaction"
|
||||||
handleClose={onClose}
|
handleClose={onClose}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
// paperClassName={cn(smallerModalSize && classes.smallerModalWindow)}
|
// paperClassName={cn(smallerModalSize && classes.smallerModalWindow)}
|
||||||
|
>
|
||||||
|
<Row align="center" grow className={classes.heading}>
|
||||||
|
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
||||||
|
Cancel transaction
|
||||||
|
</Paragraph>
|
||||||
|
<IconButton onClick={onClose} disableRipple>
|
||||||
|
<Close className={classes.closeIcon} />
|
||||||
|
</IconButton>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
<Block className={classes.container}>
|
||||||
|
<Row>
|
||||||
|
<Paragraph>
|
||||||
|
This action will cancel this transaction. A separate transaction will be performed to submit the
|
||||||
|
cancellation.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph size="sm" color="medium">
|
||||||
|
Transaction nonce:
|
||||||
|
<br />
|
||||||
|
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
|
||||||
|
</Paragraph>
|
||||||
|
</Row>
|
||||||
|
</Block>
|
||||||
|
<Row align="center" className={classes.buttonRow}>
|
||||||
|
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
||||||
|
Exit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
minWidth={214}
|
||||||
|
minHeight={42}
|
||||||
|
color="secondary"
|
||||||
|
onClick={sendReplacementTransaction}
|
||||||
>
|
>
|
||||||
<Row align="center" grow className={classes.heading}>
|
Cancel Transaction
|
||||||
<Paragraph weight="bolder" className={classes.headingText} noMargin>
|
</Button>
|
||||||
Cancel transaction
|
</Row>
|
||||||
</Paragraph>
|
</Modal>
|
||||||
<IconButton onClick={onClose} disableRipple>
|
)
|
||||||
<Close className={classes.closeIcon} />
|
}
|
||||||
</IconButton>
|
|
||||||
</Row>
|
|
||||||
<Hairline />
|
|
||||||
<Block className={classes.container}>
|
|
||||||
<Row>
|
|
||||||
<Paragraph>
|
|
||||||
This action will cancel this transaction. A separate transaction will be performed to submit the
|
|
||||||
cancellation.
|
|
||||||
</Paragraph>
|
|
||||||
<Paragraph size="sm" color="medium">
|
|
||||||
Transaction nonce:
|
|
||||||
<br />
|
|
||||||
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
|
|
||||||
</Paragraph>
|
|
||||||
</Row>
|
|
||||||
</Block>
|
|
||||||
<Row align="center" className={classes.buttonRow}>
|
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
|
||||||
Exit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
minWidth={214}
|
|
||||||
minHeight={42}
|
|
||||||
color="secondary"
|
|
||||||
onClick={sendReplacementTransaction}
|
|
||||||
>
|
|
||||||
Cancel Transaction
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</SharedSnackbarConsumer>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(CancelTxModal)
|
export default withStyles(styles)(withSnackbar(CancelTxModal))
|
||||||
|
|
|
@ -24,10 +24,7 @@ export const addSafe = createAction<string, Function, ActionReturn>(ADD_SAFE, (s
|
||||||
safe,
|
safe,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const saveSafe = (safe: Safe) => (
|
const saveSafe = (safe: Safe) => (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
||||||
dispatch: ReduxDispatch<GlobalState>,
|
|
||||||
getState: GetState<GlobalState>,
|
|
||||||
) => {
|
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const safeList = safesListSelector(state)
|
const safeList = safesListSelector(state)
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
import { createAction } from 'redux-actions'
|
import { createAction } from 'redux-actions'
|
||||||
|
|
||||||
export const ADD_TRANSACTIONS = 'ADD_TRANSACTIONS'
|
export const ADD_TRANSACTIONS = 'ADD_TRANSACTIONS'
|
||||||
|
|
||||||
export const addTransactions = createAction<string, *>(ADD_TRANSACTIONS)
|
export const addTransactions = createAction<string, *>(ADD_TRANSACTIONS)
|
||||||
|
|
|
@ -9,21 +9,29 @@ import {
|
||||||
getApprovalTransaction,
|
getApprovalTransaction,
|
||||||
getExecutionTransaction,
|
getExecutionTransaction,
|
||||||
CALL,
|
CALL,
|
||||||
type Notifications,
|
type NotifiedTransaction,
|
||||||
DEFAULT_NOTIFICATIONS,
|
|
||||||
TX_TYPE_CONFIRMATION,
|
TX_TYPE_CONFIRMATION,
|
||||||
TX_TYPE_EXECUTION,
|
TX_TYPE_EXECUTION,
|
||||||
saveTxToHistory,
|
saveTxToHistory,
|
||||||
} from '~/logic/safe/transactions'
|
} from '~/logic/safe/transactions'
|
||||||
|
import {
|
||||||
|
type Notification,
|
||||||
|
type NotificationsQueue,
|
||||||
|
getNofiticationsFromTxType,
|
||||||
|
showSnackbar,
|
||||||
|
} from '~/logic/notifications'
|
||||||
|
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
||||||
|
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||||
|
|
||||||
const createTransaction = (
|
const createTransaction = (
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
to: string,
|
to: string,
|
||||||
valueInWei: string,
|
valueInWei: string,
|
||||||
txData: string = EMPTY_DATA,
|
txData: string = EMPTY_DATA,
|
||||||
openSnackbar: Function,
|
notifiedTransaction: NotifiedTransaction,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
shouldExecute?: boolean,
|
shouldExecute?: boolean,
|
||||||
notifications?: Notifications = DEFAULT_NOTIFICATIONS,
|
|
||||||
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
||||||
const state: GlobalState = getState()
|
const state: GlobalState = getState()
|
||||||
|
|
||||||
|
@ -33,19 +41,26 @@ const createTransaction = (
|
||||||
const nonce = (await safeInstance.nonce()).toString()
|
const nonce = (await safeInstance.nonce()).toString()
|
||||||
const isExecution = threshold.toNumber() === 1 || shouldExecute
|
const isExecution = threshold.toNumber() === 1 || shouldExecute
|
||||||
|
|
||||||
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
|
const sigs = `0x000000000000000000000000${from.replace(
|
||||||
|
'0x',
|
||||||
|
'',
|
||||||
|
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||||
|
|
||||||
|
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction)
|
||||||
|
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
let pendingExecutionKey
|
||||||
|
|
||||||
let txHash
|
let txHash
|
||||||
let tx
|
let tx
|
||||||
try {
|
try {
|
||||||
if (isExecution) {
|
if (isExecution) {
|
||||||
tx = await getExecutionTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
tx = await getExecutionTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from, sigs)
|
||||||
} else {
|
} else {
|
||||||
tx = await getApprovalTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
tx = await getApprovalTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendParams = {
|
const sendParams = { from }
|
||||||
from,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not set owner management tests will fail on ganache
|
// if not set owner management tests will fail on ganache
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
sendParams.gas = '7000000'
|
sendParams.gas = '7000000'
|
||||||
|
@ -55,12 +70,21 @@ const createTransaction = (
|
||||||
.send(sendParams)
|
.send(sendParams)
|
||||||
.once('transactionHash', (hash) => {
|
.once('transactionHash', (hash) => {
|
||||||
txHash = hash
|
txHash = hash
|
||||||
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
|
closeSnackbar(beforeExecutionKey)
|
||||||
|
const pendingExecutionNotification: Notification = isExecution ? {
|
||||||
|
message: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.message,
|
||||||
|
options: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.options,
|
||||||
|
} : {
|
||||||
|
message: notificationsQueue.pendingExecution.moreConfirmationsNeeded.message,
|
||||||
|
options: notificationsQueue.pendingExecution.moreConfirmationsNeeded.options,
|
||||||
|
}
|
||||||
|
pendingExecutionKey = showSnackbar(pendingExecutionNotification, enqueueSnackbar, closeSnackbar)
|
||||||
})
|
})
|
||||||
.on('error', (error) => {
|
.on('error', (error) => {
|
||||||
console.error('Tx error: ', error)
|
console.error('Tx error: ', error)
|
||||||
})
|
})
|
||||||
.then(async (receipt) => {
|
.then(async (receipt) => {
|
||||||
|
closeSnackbar(pendingExecutionKey)
|
||||||
await saveTxToHistory(
|
await saveTxToHistory(
|
||||||
safeInstance,
|
safeInstance,
|
||||||
to,
|
to,
|
||||||
|
@ -72,17 +96,22 @@ const createTransaction = (
|
||||||
from,
|
from,
|
||||||
isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||||
)
|
)
|
||||||
|
if (isExecution) {
|
||||||
|
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
}
|
||||||
|
|
||||||
return receipt.transactionHash
|
return receipt.transactionHash
|
||||||
})
|
})
|
||||||
|
|
||||||
openSnackbar(
|
|
||||||
isExecution ? notifications.AFTER_EXECUTION : notifications.CREATED_MORE_CONFIRMATIONS_NEEDED,
|
|
||||||
'success',
|
|
||||||
)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
openSnackbar(notifications.ERROR, 'error')
|
closeSnackbar(beforeExecutionKey)
|
||||||
console.error(`Error while creating transaction: ${err}`)
|
closeSnackbar(pendingExecutionKey)
|
||||||
|
showSnackbar(notificationsQueue.afterExecutionError, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
|
const executeDataUsedSignatures = safeInstance.contract.methods
|
||||||
|
.execTransaction(to, valueInWei, txData, CALL, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
||||||
|
.encodeABI()
|
||||||
|
const errMsg = await getErrorMessage(safeInstance.address, 0, executeDataUsedSignatures, from)
|
||||||
|
console.error(`Error executing the TX: ${errMsg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchTransactions(safeAddress))
|
dispatch(fetchTransactions(safeAddress))
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
import { addTransactions } from './addTransactions'
|
import { addTransactions } from './addTransactions'
|
||||||
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
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/send'
|
import { TX_TYPE_EXECUTION } from '~/logic/safe/transactions'
|
||||||
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
||||||
|
|
||||||
let web3
|
let web3
|
||||||
|
|
|
@ -13,6 +13,13 @@ import {
|
||||||
TX_TYPE_EXECUTION,
|
TX_TYPE_EXECUTION,
|
||||||
TX_TYPE_CONFIRMATION,
|
TX_TYPE_CONFIRMATION,
|
||||||
} from '~/logic/safe/transactions'
|
} from '~/logic/safe/transactions'
|
||||||
|
import {
|
||||||
|
type Notification,
|
||||||
|
type NotificationsQueue,
|
||||||
|
getNofiticationsFromTxType,
|
||||||
|
showSnackbar,
|
||||||
|
} from '~/logic/notifications'
|
||||||
|
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
|
||||||
|
@ -38,8 +45,10 @@ const generateSignaturesFromTxConfirmations = (tx: Transaction, preApprovingOwne
|
||||||
const processTransaction = (
|
const processTransaction = (
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
openSnackbar: Function,
|
|
||||||
userAddress: string,
|
userAddress: string,
|
||||||
|
notifiedTransaction: NotifiedTransaction,
|
||||||
|
enqueueSnackbar: Function,
|
||||||
|
closeSnackbar: Function,
|
||||||
approveAndExecute?: boolean,
|
approveAndExecute?: boolean,
|
||||||
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
||||||
const state: GlobalState = getState()
|
const state: GlobalState = getState()
|
||||||
|
@ -49,54 +58,84 @@ const processTransaction = (
|
||||||
const nonce = (await safeInstance.nonce()).toString()
|
const nonce = (await safeInstance.nonce()).toString()
|
||||||
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
|
||||||
const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
|
|
||||||
|
let sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
|
||||||
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
|
if (!sigs) {
|
||||||
|
sigs = `0x000000000000000000000000${from.replace(
|
||||||
|
'0x',
|
||||||
|
'',
|
||||||
|
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction)
|
||||||
|
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
let pendingExecutionKey
|
||||||
|
|
||||||
let txHash
|
let txHash
|
||||||
let transaction
|
let transaction
|
||||||
if (shouldExecute) {
|
try {
|
||||||
transaction = await getExecutionTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from, sigs)
|
if (shouldExecute) {
|
||||||
} else {
|
transaction = await getExecutionTransaction(
|
||||||
transaction = await getApprovalTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from)
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendParams = {
|
|
||||||
from,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not set owner management tests will fail on ganache
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
|
||||||
sendParams.gas = '7000000'
|
|
||||||
}
|
|
||||||
|
|
||||||
await transaction
|
|
||||||
.send(sendParams)
|
|
||||||
.once('transactionHash', (hash) => {
|
|
||||||
txHash = hash
|
|
||||||
openSnackbar(
|
|
||||||
shouldExecute ? 'Transaction has been submitted' : 'Approval transaction has been submitted',
|
|
||||||
'success',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on('error', (error) => {
|
|
||||||
console.error('Processing transaction error: ', error)
|
|
||||||
})
|
|
||||||
.then(async (receipt) => {
|
|
||||||
await saveTxToHistory(
|
|
||||||
safeInstance,
|
safeInstance,
|
||||||
tx.recipient,
|
tx.recipient,
|
||||||
tx.value,
|
tx.value,
|
||||||
tx.data,
|
tx.data,
|
||||||
CALL,
|
CALL,
|
||||||
nonce,
|
nonce,
|
||||||
receipt.transactionHash,
|
|
||||||
from,
|
from,
|
||||||
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
sigs,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
transaction = await getApprovalTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from)
|
||||||
|
}
|
||||||
|
|
||||||
return receipt.transactionHash
|
const sendParams = { from }
|
||||||
})
|
// if not set owner management tests will fail on ganache
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
sendParams.gas = '7000000'
|
||||||
|
}
|
||||||
|
|
||||||
openSnackbar(shouldExecute ? 'Transaction has been confirmed' : 'Approval transaction has been confirmed', 'success')
|
await transaction
|
||||||
|
.send(sendParams)
|
||||||
|
.once('transactionHash', (hash) => {
|
||||||
|
txHash = hash
|
||||||
|
closeSnackbar(beforeExecutionKey)
|
||||||
|
const notification: Notification = {
|
||||||
|
message: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.message,
|
||||||
|
options: notificationsQueue.pendingExecution.noMoreConfirmationsNeeded.options,
|
||||||
|
}
|
||||||
|
pendingExecutionKey = showSnackbar(notification, enqueueSnackbar, closeSnackbar)
|
||||||
|
})
|
||||||
|
.on('error', (error) => {
|
||||||
|
console.error('Processing transaction error: ', error)
|
||||||
|
})
|
||||||
|
.then(async (receipt) => {
|
||||||
|
closeSnackbar(pendingExecutionKey)
|
||||||
|
await saveTxToHistory(
|
||||||
|
safeInstance,
|
||||||
|
tx.recipient,
|
||||||
|
tx.value,
|
||||||
|
tx.data,
|
||||||
|
CALL,
|
||||||
|
nonce,
|
||||||
|
receipt.transactionHash,
|
||||||
|
from,
|
||||||
|
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||||
|
)
|
||||||
|
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
|
return receipt.transactionHash
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
closeSnackbar(beforeExecutionKey)
|
||||||
|
closeSnackbar(pendingExecutionKey)
|
||||||
|
showSnackbar(notificationsQueue.afterExecutionError, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
|
const executeData = safeInstance.contract.methods.approveHash(txHash).encodeABI()
|
||||||
|
const errMsg = await getErrorMessage(safeInstance.address, 0, executeData, from)
|
||||||
|
console.error(`Error executing the TX: ${errMsg}`)
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(fetchTransactions(safeAddress))
|
dispatch(fetchTransactions(safeAddress))
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,18 @@ import {
|
||||||
combineReducers, createStore, applyMiddleware, compose, type Reducer, type Store,
|
combineReducers, createStore, applyMiddleware, compose, type Reducer, type Store,
|
||||||
} from 'redux'
|
} from 'redux'
|
||||||
import thunk from 'redux-thunk'
|
import thunk from 'redux-thunk'
|
||||||
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
|
|
||||||
import safe, { SAFE_REDUCER_ID, type SafeReducerState as SafeState } from '~/routes/safe/store/reducer/safe'
|
import safe, { SAFE_REDUCER_ID, type SafeReducerState as SafeState } from '~/routes/safe/store/reducer/safe'
|
||||||
import safeStorage from '~/routes/safe/store/middleware/safeStorage'
|
import safeStorage from '~/routes/safe/store/middleware/safeStorage'
|
||||||
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
|
|
||||||
import transactions, {
|
import transactions, {
|
||||||
type State as TransactionsState,
|
type State as TransactionsState,
|
||||||
TRANSACTIONS_REDUCER_ID,
|
TRANSACTIONS_REDUCER_ID,
|
||||||
} from '~/routes/safe/store/reducer/transactions'
|
} from '~/routes/safe/store/reducer/transactions'
|
||||||
|
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
|
||||||
|
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
|
||||||
|
import notifications, {
|
||||||
|
NOTIFICATIONS_REDUCER_ID,
|
||||||
|
type State as NotificationsState,
|
||||||
|
} from '~/logic/notifications/store/reducer/notifications'
|
||||||
|
|
||||||
export const history = createBrowserHistory()
|
export const history = createBrowserHistory()
|
||||||
|
|
||||||
|
@ -25,6 +29,7 @@ export type GlobalState = {
|
||||||
safes: SafeState,
|
safes: SafeState,
|
||||||
tokens: TokensState,
|
tokens: TokensState,
|
||||||
transactions: TransactionsState,
|
transactions: TransactionsState,
|
||||||
|
notifications: NotificationsState,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetState = () => GlobalState
|
export type GetState = () => GlobalState
|
||||||
|
@ -35,11 +40,13 @@ const reducers: Reducer<GlobalState> = combineReducers({
|
||||||
[SAFE_REDUCER_ID]: safe,
|
[SAFE_REDUCER_ID]: safe,
|
||||||
[TOKEN_REDUCER_ID]: tokens,
|
[TOKEN_REDUCER_ID]: tokens,
|
||||||
[TRANSACTIONS_REDUCER_ID]: transactions,
|
[TRANSACTIONS_REDUCER_ID]: transactions,
|
||||||
|
[NOTIFICATIONS_REDUCER_ID]: notifications,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const store: Store<GlobalState> = createStore(
|
export const store: Store<GlobalState> = createStore(reducers, finalCreateStore)
|
||||||
|
|
||||||
|
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(
|
||||||
reducers,
|
reducers,
|
||||||
|
localState,
|
||||||
finalCreateStore,
|
finalCreateStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(reducers, localState, finalCreateStore)
|
|
||||||
|
|
|
@ -199,18 +199,27 @@ export default createMuiTheme({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiSnackbarContent: {
|
|
||||||
root: {
|
|
||||||
boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)',
|
|
||||||
borderRadius: '3px',
|
|
||||||
color: primary,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MuiSvgIcon: {
|
MuiSvgIcon: {
|
||||||
colorSecondary: {
|
colorSecondary: {
|
||||||
color: secondaryText,
|
color: secondaryText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MuiSnackbar: {
|
||||||
|
root: {
|
||||||
|
width: '280px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiSnackbarContent: {
|
||||||
|
message: {
|
||||||
|
maxWidth: '260px',
|
||||||
|
'& img': {
|
||||||
|
marginRight: '5px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
paddingLeft: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
MuiTab: {
|
MuiTab: {
|
||||||
root: {
|
root: {
|
||||||
fontFamily: 'Averta, monospace',
|
fontFamily: 'Averta, monospace',
|
||||||
|
|
|
@ -15,8 +15,7 @@ export const storeSignature = async (safeAddress: string, nonce: number, signatu
|
||||||
const updatedSubjects = subjects.set(key, signatures)
|
const updatedSubjects = subjects.set(key, signatures)
|
||||||
await saveToStorage(signaturesKey, updatedSubjects)
|
await saveToStorage(signaturesKey, updatedSubjects)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error storing signatures in localstorage', err)
|
||||||
console.log('Error storing signatures in localstorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,7 @@ export const storeSubject = async (safeAddress: string, nonce: number, subject:
|
||||||
const updatedSubjects = subjects.set(nonce, subject)
|
const updatedSubjects = subjects.set(nonce, subject)
|
||||||
saveToStorage(key, updatedSubjects)
|
saveToStorage(key, updatedSubjects)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
console.error('Error storing transaction subject in localstorage', err)
|
||||||
console.log('Error storing transaction subject in localstorage')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
173
yarn.lock
173
yarn.lock
|
@ -418,7 +418,7 @@
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
"@babel/plugin-syntax-export-namespace-from" "^7.2.0"
|
"@babel/plugin-syntax-export-namespace-from" "^7.2.0"
|
||||||
|
|
||||||
"@babel/plugin-proposal-function-bind@^7.0.0":
|
"@babel/plugin-proposal-function-bind@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-bind/-/plugin-proposal-function-bind-7.2.0.tgz#94dc2cdc505cafc4e225c0014335a01648056bf7"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-bind/-/plugin-proposal-function-bind-7.2.0.tgz#94dc2cdc505cafc4e225c0014335a01648056bf7"
|
||||||
integrity sha512-qOFJ/eX1Is78sywwTxDcsntLOdb5ZlHVVqUz5xznq8ldAfOVIyZzp1JE2rzHnaksZIhrqMrwIpQL/qcEprnVbw==
|
integrity sha512-qOFJ/eX1Is78sywwTxDcsntLOdb5ZlHVVqUz5xznq8ldAfOVIyZzp1JE2rzHnaksZIhrqMrwIpQL/qcEprnVbw==
|
||||||
|
@ -435,7 +435,7 @@
|
||||||
"@babel/helper-wrap-function" "^7.2.0"
|
"@babel/helper-wrap-function" "^7.2.0"
|
||||||
"@babel/plugin-syntax-function-sent" "^7.2.0"
|
"@babel/plugin-syntax-function-sent" "^7.2.0"
|
||||||
|
|
||||||
"@babel/plugin-proposal-json-strings@^7.0.0", "@babel/plugin-proposal-json-strings@^7.2.0":
|
"@babel/plugin-proposal-json-strings@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
|
||||||
integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==
|
integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==
|
||||||
|
@ -443,7 +443,7 @@
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
"@babel/plugin-syntax-json-strings" "^7.2.0"
|
"@babel/plugin-syntax-json-strings" "^7.2.0"
|
||||||
|
|
||||||
"@babel/plugin-proposal-logical-assignment-operators@^7.0.0":
|
"@babel/plugin-proposal-logical-assignment-operators@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.2.0.tgz#8a5cea6c42a7c87446959e02fff5fad012c56f57"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.2.0.tgz#8a5cea6c42a7c87446959e02fff5fad012c56f57"
|
||||||
integrity sha512-0w797xwdPXKk0m3Js74hDi0mCTZplIu93MOSfb1ZLd/XFe3abWypx1QknVk0J+ohnsjYpvjH4Gwfo2i3RicB6Q==
|
integrity sha512-0w797xwdPXKk0m3Js74hDi0mCTZplIu93MOSfb1ZLd/XFe3abWypx1QknVk0J+ohnsjYpvjH4Gwfo2i3RicB6Q==
|
||||||
|
@ -459,7 +459,7 @@
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.2.0"
|
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.2.0"
|
||||||
|
|
||||||
"@babel/plugin-proposal-numeric-separator@^7.0.0":
|
"@babel/plugin-proposal-numeric-separator@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.2.0.tgz#646854daf4cd22fd6733f6076013a936310443ac"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.2.0.tgz#646854daf4cd22fd6733f6076013a936310443ac"
|
||||||
integrity sha512-DohMOGDrZiMKS7LthjUZNNcWl8TAf5BZDwZAH4wpm55FuJTHgfqPGdibg7rZDmont/8Yg0zA03IgT6XLeP+4sg==
|
integrity sha512-DohMOGDrZiMKS7LthjUZNNcWl8TAf5BZDwZAH4wpm55FuJTHgfqPGdibg7rZDmont/8Yg0zA03IgT6XLeP+4sg==
|
||||||
|
@ -515,7 +515,7 @@
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
"@babel/plugin-syntax-pipeline-operator" "^7.5.0"
|
"@babel/plugin-syntax-pipeline-operator" "^7.5.0"
|
||||||
|
|
||||||
"@babel/plugin-proposal-throw-expressions@^7.0.0":
|
"@babel/plugin-proposal-throw-expressions@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.2.0.tgz#2d9e452d370f139000e51db65d0a85dc60c64739"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.2.0.tgz#2d9e452d370f139000e51db65d0a85dc60c64739"
|
||||||
integrity sha512-adsydM8DQF4i5DLNO4ySAU5VtHTPewOtNBV3u7F4lNMPADFF9bWQ+iDtUUe8+033cYCUz+bFlQdXQJmJOwoLpw==
|
integrity sha512-adsydM8DQF4i5DLNO4ySAU5VtHTPewOtNBV3u7F4lNMPADFF9bWQ+iDtUUe8+033cYCUz+bFlQdXQJmJOwoLpw==
|
||||||
|
@ -562,7 +562,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
|
||||||
"@babel/plugin-syntax-dynamic-import@7.2.0", "@babel/plugin-syntax-dynamic-import@^7.0.0", "@babel/plugin-syntax-dynamic-import@^7.2.0":
|
"@babel/plugin-syntax-dynamic-import@7.2.0", "@babel/plugin-syntax-dynamic-import@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
|
||||||
integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
|
integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
|
||||||
|
@ -604,7 +604,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
|
||||||
"@babel/plugin-syntax-import-meta@^7.0.0":
|
"@babel/plugin-syntax-import-meta@^7.2.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.2.0.tgz#2333ef4b875553a3bcd1e93f8ebc09f5b9213a40"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.2.0.tgz#2333ef4b875553a3bcd1e93f8ebc09f5b9213a40"
|
||||||
integrity sha512-Hq6kFSZD7+PHkmBN8bCpHR6J8QEoCuEV/B38AIQscYjgMZkGlXB7cHNFzP5jR4RCh5545yP1ujHdmO7hAgKtBA==
|
integrity sha512-Hq6kFSZD7+PHkmBN8bCpHR6J8QEoCuEV/B38AIQscYjgMZkGlXB7cHNFzP5jR4RCh5545yP1ujHdmO7hAgKtBA==
|
||||||
|
@ -1269,7 +1269,7 @@
|
||||||
js-levenshtein "^1.1.3"
|
js-levenshtein "^1.1.3"
|
||||||
semver "^5.5.0"
|
semver "^5.5.0"
|
||||||
|
|
||||||
"@babel/preset-flow@^7.0.0", "@babel/preset-flow@^7.0.0-beta.40":
|
"@babel/preset-flow@^7.0.0":
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2"
|
resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2"
|
||||||
integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ==
|
integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ==
|
||||||
|
@ -1277,7 +1277,7 @@
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
"@babel/plugin-transform-flow-strip-types" "^7.0.0"
|
"@babel/plugin-transform-flow-strip-types" "^7.0.0"
|
||||||
|
|
||||||
"@babel/preset-react@7.0.0", "@babel/preset-react@^7.0.0", "@babel/preset-react@^7.0.0-beta.40":
|
"@babel/preset-react@7.0.0", "@babel/preset-react@^7.0.0":
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0"
|
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0"
|
||||||
integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==
|
integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==
|
||||||
|
@ -1906,7 +1906,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.0.2.tgz#1d94f02800b094753f9271c206a26c2a06ca14ee"
|
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.0.2.tgz#1d94f02800b094753f9271c206a26c2a06ca14ee"
|
||||||
integrity sha512-8/qcMh15507AnXJ3lBeuhsdFwnWQqnp68EpUuHlYPixJ5vjVmls7/Jq48cnUlrZI8Jd9U1jkhfCl0gaT5KMgVw==
|
integrity sha512-8/qcMh15507AnXJ3lBeuhsdFwnWQqnp68EpUuHlYPixJ5vjVmls7/Jq48cnUlrZI8Jd9U1jkhfCl0gaT5KMgVw==
|
||||||
|
|
||||||
"@sambego/storybook-state@^1.0.7":
|
"@sambego/storybook-state@^1.3.6":
|
||||||
version "1.3.6"
|
version "1.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/@sambego/storybook-state/-/storybook-state-1.3.6.tgz#9a6511095d200b8ab2d6bc39def81c90312dd420"
|
resolved "https://registry.yarnpkg.com/@sambego/storybook-state/-/storybook-state-1.3.6.tgz#9a6511095d200b8ab2d6bc39def81c90312dd420"
|
||||||
integrity sha512-bTUE1ZTtI9ICyqz6l5gtUfo0/W77fPP7KOAd/HI1jM7m1Jxjxs1k1Qbcrqmxg1vaHemlXVkvxVCZf8BT9RzxGw==
|
integrity sha512-bTUE1ZTtI9ICyqz6l5gtUfo0/W77fPP7KOAd/HI1jM7m1Jxjxs1k1Qbcrqmxg1vaHemlXVkvxVCZf8BT9RzxGw==
|
||||||
|
@ -3037,6 +3037,11 @@ acorn-jsx@^5.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
|
||||||
integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
|
integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
|
||||||
|
|
||||||
|
acorn-jsx@^5.0.2:
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f"
|
||||||
|
integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==
|
||||||
|
|
||||||
acorn-walk@^6.0.1, acorn-walk@^6.1.1:
|
acorn-walk@^6.0.1, acorn-walk@^6.1.1:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
|
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
|
||||||
|
@ -3062,6 +3067,11 @@ acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.0, acorn@^6.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
|
||||||
integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
|
integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
|
||||||
|
|
||||||
|
acorn@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a"
|
||||||
|
integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==
|
||||||
|
|
||||||
address@1.0.3:
|
address@1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
|
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
|
||||||
|
@ -3915,7 +3925,7 @@ babel-plugin-dynamic-import-node@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
object.assign "^4.1.0"
|
object.assign "^4.1.0"
|
||||||
|
|
||||||
babel-plugin-dynamic-import-node@^2.2.0, babel-plugin-dynamic-import-node@^2.3.0:
|
babel-plugin-dynamic-import-node@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
|
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
|
||||||
integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
|
integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
|
||||||
|
@ -5512,7 +5522,7 @@ class-utils@^0.3.5:
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@^2.2.5:
|
classnames@^2.2.5, classnames@^2.2.6:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||||
|
@ -6738,7 +6748,7 @@ detect-port-alt@1.1.6:
|
||||||
address "^1.0.1"
|
address "^1.0.1"
|
||||||
debug "^2.6.0"
|
debug "^2.6.0"
|
||||||
|
|
||||||
detect-port@^1.2.2, detect-port@^1.3.0:
|
detect-port@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1"
|
resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1"
|
||||||
integrity sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==
|
integrity sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==
|
||||||
|
@ -7359,6 +7369,14 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3:
|
||||||
esrecurse "^4.1.0"
|
esrecurse "^4.1.0"
|
||||||
estraverse "^4.1.1"
|
estraverse "^4.1.1"
|
||||||
|
|
||||||
|
eslint-scope@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
|
||||||
|
integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
|
||||||
|
dependencies:
|
||||||
|
esrecurse "^4.1.0"
|
||||||
|
estraverse "^4.1.1"
|
||||||
|
|
||||||
eslint-utils@^1.3.1:
|
eslint-utils@^1.3.1:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.0.tgz#e2c3c8dba768425f897cf0f9e51fe2e241485d4c"
|
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.0.tgz#e2c3c8dba768425f897cf0f9e51fe2e241485d4c"
|
||||||
|
@ -7366,12 +7384,67 @@ eslint-utils@^1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^1.0.0"
|
eslint-visitor-keys "^1.0.0"
|
||||||
|
|
||||||
|
eslint-utils@^1.4.2:
|
||||||
|
version "1.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
|
||||||
|
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
|
||||||
|
dependencies:
|
||||||
|
eslint-visitor-keys "^1.0.0"
|
||||||
|
|
||||||
eslint-visitor-keys@^1.0.0:
|
eslint-visitor-keys@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||||
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
|
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
|
||||||
|
|
||||||
eslint@5.16.0, eslint@^5.0.0, eslint@^5.5.0:
|
eslint-visitor-keys@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
|
||||||
|
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
|
||||||
|
|
||||||
|
eslint@6.4.0:
|
||||||
|
version "6.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.4.0.tgz#5aa9227c3fbe921982b2eda94ba0d7fae858611a"
|
||||||
|
integrity sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "^7.0.0"
|
||||||
|
ajv "^6.10.0"
|
||||||
|
chalk "^2.1.0"
|
||||||
|
cross-spawn "^6.0.5"
|
||||||
|
debug "^4.0.1"
|
||||||
|
doctrine "^3.0.0"
|
||||||
|
eslint-scope "^5.0.0"
|
||||||
|
eslint-utils "^1.4.2"
|
||||||
|
eslint-visitor-keys "^1.1.0"
|
||||||
|
espree "^6.1.1"
|
||||||
|
esquery "^1.0.1"
|
||||||
|
esutils "^2.0.2"
|
||||||
|
file-entry-cache "^5.0.1"
|
||||||
|
functional-red-black-tree "^1.0.1"
|
||||||
|
glob-parent "^5.0.0"
|
||||||
|
globals "^11.7.0"
|
||||||
|
ignore "^4.0.6"
|
||||||
|
import-fresh "^3.0.0"
|
||||||
|
imurmurhash "^0.1.4"
|
||||||
|
inquirer "^6.4.1"
|
||||||
|
is-glob "^4.0.0"
|
||||||
|
js-yaml "^3.13.1"
|
||||||
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
|
levn "^0.3.0"
|
||||||
|
lodash "^4.17.14"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
natural-compare "^1.4.0"
|
||||||
|
optionator "^0.8.2"
|
||||||
|
progress "^2.0.0"
|
||||||
|
regexpp "^2.0.1"
|
||||||
|
semver "^6.1.2"
|
||||||
|
strip-ansi "^5.2.0"
|
||||||
|
strip-json-comments "^3.0.1"
|
||||||
|
table "^5.2.3"
|
||||||
|
text-table "^0.2.0"
|
||||||
|
v8-compile-cache "^2.0.3"
|
||||||
|
|
||||||
|
eslint@^5.0.0, eslint@^5.5.0:
|
||||||
version "5.16.0"
|
version "5.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
|
||||||
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==
|
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==
|
||||||
|
@ -7430,6 +7503,15 @@ espree@^5.0.1:
|
||||||
acorn-jsx "^5.0.0"
|
acorn-jsx "^5.0.0"
|
||||||
eslint-visitor-keys "^1.0.0"
|
eslint-visitor-keys "^1.0.0"
|
||||||
|
|
||||||
|
espree@^6.1.1:
|
||||||
|
version "6.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de"
|
||||||
|
integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==
|
||||||
|
dependencies:
|
||||||
|
acorn "^7.0.0"
|
||||||
|
acorn-jsx "^5.0.2"
|
||||||
|
eslint-visitor-keys "^1.1.0"
|
||||||
|
|
||||||
esprima@^3.1.3, esprima@~3.1.0:
|
esprima@^3.1.3, esprima@~3.1.0:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
||||||
|
@ -9004,6 +9086,13 @@ glob-parent@^3.1.0:
|
||||||
is-glob "^3.1.0"
|
is-glob "^3.1.0"
|
||||||
path-dirname "^1.0.0"
|
path-dirname "^1.0.0"
|
||||||
|
|
||||||
|
glob-parent@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
|
||||||
|
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
glob-stream@^6.1.0:
|
glob-stream@^6.1.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
|
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
|
||||||
|
@ -9637,7 +9726,7 @@ html-tag-names@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.4.tgz#51c559e36a077b5eb6c71e6cb49b1d70fffc9124"
|
resolved "https://registry.yarnpkg.com/html-tag-names/-/html-tag-names-1.1.4.tgz#51c559e36a077b5eb6c71e6cb49b1d70fffc9124"
|
||||||
integrity sha512-QCOY1/oHmo2BNwsTzuYlW51JLXSxfmMvve+2/9i2cbhxXxT6SuhsUWzcIoMwUi0HZW/NIQBSyJaj7fbcsimoKg==
|
integrity sha512-QCOY1/oHmo2BNwsTzuYlW51JLXSxfmMvve+2/9i2cbhxXxT6SuhsUWzcIoMwUi0HZW/NIQBSyJaj7fbcsimoKg==
|
||||||
|
|
||||||
html-webpack-plugin@^3.0.4:
|
html-webpack-plugin@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
|
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
|
||||||
integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s=
|
integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s=
|
||||||
|
@ -10001,6 +10090,25 @@ inquirer@^6.2.0, inquirer@^6.2.2:
|
||||||
strip-ansi "^5.1.0"
|
strip-ansi "^5.1.0"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
|
|
||||||
|
inquirer@^6.4.1:
|
||||||
|
version "6.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
|
||||||
|
integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
|
||||||
|
dependencies:
|
||||||
|
ansi-escapes "^3.2.0"
|
||||||
|
chalk "^2.4.2"
|
||||||
|
cli-cursor "^2.1.0"
|
||||||
|
cli-width "^2.0.0"
|
||||||
|
external-editor "^3.0.3"
|
||||||
|
figures "^2.0.0"
|
||||||
|
lodash "^4.17.12"
|
||||||
|
mute-stream "0.0.7"
|
||||||
|
run-async "^2.2.0"
|
||||||
|
rxjs "^6.4.0"
|
||||||
|
string-width "^2.1.0"
|
||||||
|
strip-ansi "^5.1.0"
|
||||||
|
through "^2.3.6"
|
||||||
|
|
||||||
internal-ip@^4.3.0:
|
internal-ip@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
|
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
|
||||||
|
@ -10291,7 +10399,7 @@ is-glob@^3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
is-extglob "^2.1.0"
|
||||||
|
|
||||||
is-glob@^4.0.0:
|
is-glob@^4.0.0, is-glob@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||||
|
@ -12785,6 +12893,15 @@ normalize-url@^4.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee"
|
||||||
integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==
|
integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==
|
||||||
|
|
||||||
|
"notistack@https://github.com/gnosis/notistack.git#v0.9.4":
|
||||||
|
version "0.9.4"
|
||||||
|
resolved "https://github.com/gnosis/notistack.git#077aa51fd066f2c3198b2f5ce773f741e21f24df"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.6"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^16.9.0"
|
||||||
|
|
||||||
now-and-later@^2.0.0:
|
now-and-later@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c"
|
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c"
|
||||||
|
@ -14776,7 +14893,7 @@ react-redux@7.1.1:
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-is "^16.9.0"
|
react-is "^16.9.0"
|
||||||
|
|
||||||
react-router-dom@5.1.0:
|
react-router-dom@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.0.tgz#48ad018d71fb7835212587e4c90bd2e3d2417e31"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.0.tgz#48ad018d71fb7835212587e4c90bd2e3d2417e31"
|
||||||
integrity sha512-OkxKbMKjO7IkYqnoaZNX19MnwgjhxwZE871cPUTq0YU2wpIw7QwGxSnSoNRMOa7wO1TwvJJMFpgiEB4C/gVhTw==
|
integrity sha512-OkxKbMKjO7IkYqnoaZNX19MnwgjhxwZE871cPUTq0YU2wpIw7QwGxSnSoNRMOa7wO1TwvJJMFpgiEB4C/gVhTw==
|
||||||
|
@ -15108,7 +15225,7 @@ reduce-reducers@^0.4.3:
|
||||||
resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c"
|
resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c"
|
||||||
integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==
|
integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==
|
||||||
|
|
||||||
redux-actions@^2.3.0:
|
redux-actions@^2.6.5:
|
||||||
version "2.6.5"
|
version "2.6.5"
|
||||||
resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e"
|
resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e"
|
||||||
integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw==
|
integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw==
|
||||||
|
@ -15152,7 +15269,7 @@ redux-saga@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@redux-saga/core" "^1.0.0"
|
"@redux-saga/core" "^1.0.0"
|
||||||
|
|
||||||
redux-thunk@^2.2.0:
|
redux-thunk@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||||
|
@ -15963,7 +16080,7 @@ semver@6.2.0, semver@^6.0.0, semver@^6.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db"
|
||||||
integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==
|
integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==
|
||||||
|
|
||||||
semver@^6.2.0, semver@^6.3.0:
|
semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
@ -16547,7 +16664,7 @@ storybook-host@5.1.0:
|
||||||
ramda "^0.25.0"
|
ramda "^0.25.0"
|
||||||
tinycolor2 "^1.4.1"
|
tinycolor2 "^1.4.1"
|
||||||
|
|
||||||
storybook-router@^0.3.3:
|
storybook-router@^0.3.4:
|
||||||
version "0.3.4"
|
version "0.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/storybook-router/-/storybook-router-0.3.4.tgz#27c0c0de5eafa03b9003a850ac40e2b0c2a3ee65"
|
resolved "https://registry.yarnpkg.com/storybook-router/-/storybook-router-0.3.4.tgz#27c0c0de5eafa03b9003a850ac40e2b0c2a3ee65"
|
||||||
integrity sha512-WU8kyx06R5zFa3KT1TZey2fOadj0nFhWs5yuq3iVcfSbUhtwSg/QNGF7V6IXjiBOtYqmvN2/hlUnYneC/oQ16w==
|
integrity sha512-WU8kyx06R5zFa3KT1TZey2fOadj0nFhWs5yuq3iVcfSbUhtwSg/QNGF7V6IXjiBOtYqmvN2/hlUnYneC/oQ16w==
|
||||||
|
@ -16764,6 +16881,11 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||||
|
|
||||||
|
strip-json-comments@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
|
||||||
|
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
|
||||||
|
|
||||||
style-loader@1.0.0:
|
style-loader@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82"
|
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82"
|
||||||
|
@ -18357,6 +18479,11 @@ v8-compile-cache@2.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
||||||
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
|
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
|
||||||
|
|
||||||
|
v8-compile-cache@^2.0.3:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
|
||||||
|
integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
|
||||||
|
|
||||||
v8flags@^2.1.1:
|
v8flags@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
|
||||||
|
@ -19662,7 +19789,7 @@ webpack-log@^2.0.0:
|
||||||
ansi-colors "^3.0.0"
|
ansi-colors "^3.0.0"
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
|
|
||||||
webpack-manifest-plugin@2.1.1:
|
webpack-manifest-plugin@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.1.1.tgz#6b3e280327815b83152c79f42d0ca13b665773c4"
|
resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.1.1.tgz#6b3e280327815b83152c79f42d0ca13b665773c4"
|
||||||
integrity sha512-2zqJ6mvc3yoiqfDjghAIpljhLSDh/G7vqGrzYcYqqRCd/ZZZCAuc/YPE5xG0LGpLgDJRhUNV1H+znyyhIxahzA==
|
integrity sha512-2zqJ6mvc3yoiqfDjghAIpljhLSDh/G7vqGrzYcYqqRCd/ZZZCAuc/YPE5xG0LGpLgDJRhUNV1H+znyyhIxahzA==
|
||||||
|
@ -20267,4 +20394,4 @@ yauzl@^2.4.2:
|
||||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-crc32 "~0.2.3"
|
buffer-crc32 "~0.2.3"
|
||||||
fd-slicer "~1.1.0"
|
fd-slicer "~1.1.0"
|
||||||
|
|
Loading…
Reference in New Issue