Merge pull request #138 from gnosis/126-onboarding-improvements

Feature #126: Onboarding improvements
This commit is contained in:
Mikhail Mikheev 2019-07-26 14:05:23 +04:00 committed by GitHub
commit 6d31f1b0e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 780 additions and 1136 deletions

View File

@ -31,7 +31,7 @@
"dependencies": {
"@gnosis.pm/safe-contracts": "^1.0.0",
"@gnosis.pm/util-contracts": "2.0.1",
"@material-ui/core": "4.2.0",
"@material-ui/core": "4.2.1",
"@material-ui/icons": "4.2.1",
"@testing-library/jest-dom": "^4.0.0",
"@welldone-software/why-did-you-render": "3.2.1",
@ -50,8 +50,9 @@
"react-dom": "^16.8.6",
"react-final-form": "6.3.0",
"react-final-form-listeners": "^1.0.2",
"react-hot-loader": "4.12.7",
"react-hot-loader": "4.12.8",
"react-infinite-scroll-component": "^4.5.2",
"react-qr-reader": "^2.2.1",
"react-redux": "7.1.0",
"react-router-dom": "^5.0.1",
"recompose": "^0.30.0",
@ -62,9 +63,9 @@
"web3": "1.0.0-beta.37"
},
"devDependencies": {
"@babel/cli": "7.5.0",
"@babel/core": "7.5.4",
"@babel/plugin-proposal-class-properties": "7.5.0",
"@babel/cli": "7.5.5",
"@babel/core": "7.5.5",
"@babel/plugin-proposal-class-properties": "7.5.5",
"@babel/plugin-proposal-decorators": "7.4.4",
"@babel/plugin-proposal-do-expressions": "7.5.0",
"@babel/plugin-proposal-export-default-from": "7.5.2",
@ -83,7 +84,7 @@
"@babel/plugin-transform-member-expression-literals": "^7.2.0",
"@babel/plugin-transform-property-literals": "^7.2.0",
"@babel/polyfill": "7.4.4",
"@babel/preset-env": "7.5.4",
"@babel/preset-env": "7.5.5",
"@babel/preset-flow": "^7.0.0-beta.40",
"@babel/preset-react": "^7.0.0-beta.40",
"@sambego/storybook-state": "^1.0.7",
@ -101,19 +102,19 @@
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
"babel-plugin-transform-es3-property-literals": "^6.22.0",
"classnames": "^2.2.5",
"css-loader": "3.0.0",
"css-loader": "3.1.0",
"detect-port": "^1.2.2",
"eslint": "5.16.0",
"eslint-config-airbnb": "17.1.1",
"eslint-plugin-flowtype": "3.11.1",
"eslint-plugin-import": "2.18.0",
"eslint-plugin-jest": "22.9.0",
"eslint-plugin-flowtype": "3.12.1",
"eslint-plugin-import": "2.18.1",
"eslint-plugin-jest": "22.11.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.14.2",
"ethereumjs-abi": "^0.6.7",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "4.0.0",
"flow-bin": "0.102.0",
"file-loader": "4.1.0",
"flow-bin": "0.103.0",
"fs-extra": "8.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.0.4",
@ -122,7 +123,7 @@
"json-loader": "^0.5.7",
"mini-css-extract-plugin": "0.8.0",
"postcss-loader": "^3.0.0",
"postcss-mixins": "^6.2.0",
"postcss-mixins": "6.2.2",
"postcss-simple-vars": "^5.0.2",
"pre-commit": "^1.2.2",
"prettier-eslint-cli": "5.0.0",
@ -130,11 +131,11 @@
"storybook-host": "5.1.0",
"storybook-router": "^0.3.3",
"style-loader": "^0.23.1",
"truffle": "5.0.27",
"truffle-contract": "4.0.24",
"truffle-solidity-loader": "0.1.26",
"truffle": "5.0.28",
"truffle-contract": "4.0.25",
"truffle-solidity-loader": "0.1.27",
"uglifyjs-webpack-plugin": "2.1.3",
"webpack": "4.35.3",
"webpack": "4.36.1",
"webpack-bundle-analyzer": "3.3.2",
"webpack-cli": "3.3.6",
"webpack-dev-server": "3.7.2",

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
<title>Group</title>
<desc>Created with Sketch.</desc>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Icon-/-QR-Icon" transform="translate(-11.000000, -11.000000)" fill="#a2a8ba">
<g id="QR-Icon" transform="translate(2.000000, 2.000000)">
<g id="Group" transform="translate(9.000000, 9.000000)">
<path d="M2,2 L2,4 L4,4 L4,2 L2,2 Z M6,0 L6,6 L0,6 L0,0 L6,0 Z" id="Rectangle" fill-rule="nonzero"></path>
<path d="M2,12 L2,14 L4,14 L4,12 L2,12 Z M6,10 L6,16 L0,16 L0,10 L6,10 Z" id="Rectangle-Copy-2" fill-rule="nonzero"></path>
<path d="M12,2 L12,4 L14,4 L14,2 L12,2 Z M16,0 L16,6 L10,6 L10,0 L16,0 Z" id="Rectangle-Copy" fill-rule="nonzero"></path>
<rect id="Rectangle" x="7" y="2" width="2" height="4"></rect>
<path d="M7,9 L5,9 L5,7 L7,7 L9,7 L9,11 L7,11 L7,9 Z" id="Combined-Shape"></path>
<rect id="Rectangle-Copy-4" x="0" y="7" width="2" height="2"></rect>
<rect id="Rectangle-Copy-5" x="10" y="7" width="2" height="2"></rect>
<rect id="Rectangle-Copy-6" x="14" y="7" width="2" height="2"></rect>
<rect id="Rectangle-Copy-7" x="12" y="9" width="2" height="2"></rect>
<rect id="Rectangle-Copy-10" x="12" y="14" width="2" height="2"></rect>
<path d="M9,12 L10,12 L10,11 L12,11 L12,14 L10,14 L9,14 L9,16 L7,16 L7,12 L9,12 Z" id="Combined-Shape"></path>
<rect id="Rectangle-Copy-9" x="14" y="11" width="2" height="3"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,7 +1,7 @@
// @flow
import * as React from 'react'
import Page from '~/components/layout/Page'
import CircularProgress from '@material-ui/core/CircularProgress'
import Page from '~/components/layout/Page'
const centerStyle = {
margin: 'auto 0',

View File

@ -1,16 +1,17 @@
// @flow
import * as React from 'react'
import Stepper from '@material-ui/core/Stepper'
import FormStep from '@material-ui/core/Step'
import StepLabel from '@material-ui/core/StepLabel'
import StepContent from '@material-ui/core/StepContent'
import { withStyles } from '@material-ui/core/styles'
import * as React from 'react'
import GnoForm from '~/components/forms/GnoForm'
import Hairline from '~/components/layout/Hairline'
import Button from '~/components/layout/Button'
import { history } from '~/store'
import Controls from './Controls'
const { useState, useEffect } = React
export { default as Step } from './Step'
type Props = {
@ -18,20 +19,15 @@ type Props = {
onSubmit: (values: Object) => Promise<void>,
children: React.Node,
classes: Object,
onReset?: () => void,
initialValues?: Object,
disabledWhenValidating?: boolean,
mutators?: Object,
testId?: string,
}
type State = {
page: number,
values: Object,
}
type PageProps = {
children: Function,
prepareNextInitialProps: (values: Object) => {},
prepareNextInitialProps?: (values: Object) => {},
}
const transitionProps = {
@ -41,151 +37,124 @@ const transitionProps = {
},
}
class GnoStepper extends React.PureComponent<Props, State> {
static Page = ({ children }: PageProps) => children
export const StepperPage = ({ children }: PageProps) => children
static FinishButton = ({
component, to, title, ...props
}) => (
<Button component={component} to={to} variant="contained" color="primary" {...props}>
{title}
</Button>
)
const GnoStepper = (props: Props) => {
const [page, setPage] = useState<number>(0)
const [values, setValues] = useState<Object>({})
constructor(props: Props) {
super(props)
this.state = {
page: 0,
values: props.initialValues || {},
useEffect(() => {
if (props.initialValues) {
setValues(props.initialValues)
}
}, [])
const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props
const updateInitialProps = (newInitialProps) => {
setValues(newInitialProps)
}
onReset = () => {
const { onReset, initialValues } = this.props
if (onReset) {
onReset()
}
const getActivePageFrom = (pages: React.Node) => {
const activePageProps = getPageProps(pages)
const { children, ...restProps } = activePageProps
this.setState(() => ({
page: 0,
values: initialValues || {},
}))
return children({ ...restProps, updateInitialProps })
}
getPageProps = (pages: React.Node): PageProps => {
const { page } = this.state
return React.Children.toArray(pages)[page].props
}
getActivePageFrom = (pages: React.Node) => {
const activePageProps = this.getPageProps(pages)
const { children, ...props } = activePageProps
return children({ ...props, updateInitialProps: this.updateInitialProps })
}
updateInitialProps = (values) => {
this.setState({ values })
}
validate = (values: Object) => {
const { children } = this.props
const { page } = this.state
const validate = (valuesToValidate: Object) => {
const { children } = props
const activePage = React.Children.toArray(children)[page]
return activePage.props.validate ? activePage.props.validate(values) : {}
return activePage.props.validate ? activePage.props.validate(valuesToValidate) : {}
}
next = async (values: Object) => {
const { children } = this.props
const activePageProps = this.getPageProps(children)
const next = async (formValues: Object) => {
const { children } = props
const activePageProps = getPageProps(children)
const { prepareNextInitialProps } = activePageProps
let pageInitialProps
if (prepareNextInitialProps) {
pageInitialProps = await prepareNextInitialProps(values)
pageInitialProps = await prepareNextInitialProps(formValues)
}
const finalValues = { ...values, ...pageInitialProps }
this.setState(state => ({
page: Math.min(state.page + 1, React.Children.count(children) - 1),
values: finalValues,
}))
const finalValues = { ...formValues, ...pageInitialProps }
setValues(finalValues)
setPage(Math.min(page + 1, React.Children.count(children) - 1))
}
previous = () => {
const { page } = this.state
const previous = () => {
const firstPage = page === 0
if (firstPage) {
return history.goBack()
}
return this.setState(state => ({
page: Math.max(state.page - 1, 0),
}))
return setPage(Math.max(page - 1, 0))
}
handleSubmit = async (values: Object) => {
const { children, onSubmit } = this.props
const { page } = this.state
const handleSubmit = async (formValues: Object) => {
const { children, onSubmit } = props
const isLastPage = page === React.Children.count(children) - 1
if (isLastPage) {
return onSubmit(values)
return onSubmit(formValues)
}
return this.next(values)
return next(formValues)
}
isLastPage = (page) => {
const { steps } = this.props
return page === steps.length - 1
const isLastPage = (pageNumber) => {
const { steps } = props
return pageNumber === steps.length - 1
}
render() {
const {
steps, children, classes, disabledWhenValidating = false, testId,
} = this.props
const { page, values } = this.state
const activePage = this.getActivePageFrom(children)
const lastPage = this.isLastPage(page)
const penultimate = this.isLastPage(page + 1)
const {
steps, children, classes, disabledWhenValidating = false, testId, mutators,
} = props
const activePage = getActivePageFrom(children)
return (
<React.Fragment>
<GnoForm onSubmit={this.handleSubmit} initialValues={values} validation={this.validate} testId={testId}>
{(submitting: boolean, validating: boolean, ...rest: any) => {
const disabled = disabledWhenValidating ? submitting || validating : submitting
const controls = (
<React.Fragment>
<Hairline />
<Controls
disabled={disabled}
onPrevious={this.previous}
firstPage={page === 0}
lastPage={lastPage}
penultimate={penultimate}
/>
</React.Fragment>
)
const lastPage = isLastPage(page)
const penultimate = isLastPage(page + 1)
return (
<Stepper classes={{ root: classes.root }} activeStep={page} orientation="vertical">
{steps.map(label => (
<FormStep key={label}>
<StepLabel>{label}</StepLabel>
<StepContent TransitionProps={transitionProps}>{activePage(controls, ...rest)}</StepContent>
</FormStep>
))}
</Stepper>
)
}}
</GnoForm>
</React.Fragment>
)
}
return (
<React.Fragment>
<GnoForm
onSubmit={handleSubmit}
initialValues={values}
validation={validate}
testId={testId}
formMutators={mutators}
>
{(submitting: boolean, validating: boolean, ...rest: any) => {
const disabled = disabledWhenValidating ? submitting || validating : submitting
const controls = (
<React.Fragment>
<Hairline />
<Controls
disabled={disabled}
onPrevious={previous}
firstPage={page === 0}
lastPage={lastPage}
penultimate={penultimate}
/>
</React.Fragment>
)
return (
<Stepper classes={{ root: classes.root }} activeStep={page} orientation="vertical">
{steps.map(label => (
<FormStep key={label}>
<StepLabel>{label}</StepLabel>
<StepContent TransitionProps={transitionProps}>{activePage(controls, ...rest)}</StepContent>
</FormStep>
))}
</Stepper>
)
}}
</GnoForm>
</React.Fragment>
)
}
const styles = {

View File

@ -1,10 +1,12 @@
// @flow
import classNames from 'classnames/bind'
import React, { PureComponent } from 'react'
import * as React from 'react'
import { capitalize } from '~/utils/css'
import { type Size } from '~/theme/size'
import styles from './index.scss'
const { PureComponent } = React
const cx = classNames.bind(styles)
type Props = {

View File

@ -2,7 +2,7 @@
import * as React from 'react'
import ChevronLeft from '@material-ui/icons/ChevronLeft'
import IconButton from '@material-ui/core/IconButton'
import Stepper from '~/components/Stepper'
import Stepper, { StepperPage } from '~/components/Stepper'
import Block from '~/components/layout/Block'
import Heading from '~/components/layout/Heading'
import Row from '~/components/layout/Row'
@ -46,11 +46,11 @@ const Layout = ({
<Heading tag="h2">Load existing Safe</Heading>
</Row>
<Stepper onSubmit={onLoadSafeSubmit} steps={steps} initialValues={initialValues} testId="load-safe-form">
<Stepper.Page validate={safeFieldsValidation}>{DetailsForm}</Stepper.Page>
<Stepper.Page network={network}>{OwnerList}</Stepper.Page>
<Stepper.Page network={network} userAddress={userAddress}>
<StepperPage validate={safeFieldsValidation}>{DetailsForm}</StepperPage>
<StepperPage network={network}>{OwnerList}</StepperPage>
<StepperPage network={network} userAddress={userAddress}>
{ReviewInformation}
</Stepper.Page>
</StepperPage>
</Stepper>
</Block>
) : (

View File

@ -1,20 +1,19 @@
// @flow
import * as React from 'react'
import Stepper from '~/components/Stepper'
import ChevronLeft from '@material-ui/icons/ChevronLeft'
import IconButton from '@material-ui/core/IconButton'
import Stepper, { StepperPage } from '~/components/Stepper'
import Block from '~/components/layout/Block'
import Heading from '~/components/layout/Heading'
import Row from '~/components/layout/Row'
import IconButton from '@material-ui/core/IconButton'
import Review from '~/routes/open/components/ReviewInformation'
import ChevronLeft from '@material-ui/icons/ChevronLeft'
import SafeNameField from '~/routes/open/components/SafeNameForm'
import SafeThresholdField, { safeFieldsValidation } from '~/routes/open/components/SafeThresholdForm'
import SafeOwnersFields from '~/routes/open/components/SafeOwnersForm'
import SafeOwnersFields from '~/routes/open/components/SafeOwnersConfirmationsForm'
import { getOwnerNameBy, getOwnerAddressBy, FIELD_CONFIRMATIONS } from '~/routes/open/components/fields'
import { history } from '~/store'
import { secondary } from '~/theme/variables'
const getSteps = () => ['Start', 'Owners', 'Confirmations', 'Review']
const getSteps = () => ['Start', 'Owners and confirmations', 'Review']
const initialValuesFrom = (userAccount: string) => ({
[getOwnerNameBy(0)]: 'My Metamask (me)',
@ -39,6 +38,12 @@ const back = () => {
history.goBack()
}
const formMutators = {
setValue: ([field, value], state, { changeValue }) => {
changeValue(state, field, () => value)
},
}
const Layout = ({
provider, userAccount, onCallSafeContractSubmit, network,
}: Props) => {
@ -55,15 +60,20 @@ const Layout = ({
</IconButton>
<Heading tag="h2">Create New Safe</Heading>
</Row>
<Stepper onSubmit={onCallSafeContractSubmit} steps={steps} initialValues={initialValues} testId="create-safe-form">
<Stepper.Page>{SafeNameField}</Stepper.Page>
<Stepper.Page>{SafeOwnersFields}</Stepper.Page>
<Stepper.Page validate={safeFieldsValidation}>{SafeThresholdField}</Stepper.Page>
<Stepper.Page network={network}>{Review}</Stepper.Page>
<Stepper
onSubmit={onCallSafeContractSubmit}
steps={steps}
initialValues={initialValues}
mutators={formMutators}
testId="create-safe-form"
>
<StepperPage>{SafeNameField}</StepperPage>
<StepperPage>{SafeOwnersFields}</StepperPage>
<StepperPage network={network}>{Review}</StepperPage>
</Stepper>
</Block>
) : (
<div>No metamask detected</div>
<div>No web3 provider detected</div>
)}
</React.Fragment>
)

View File

@ -129,7 +129,11 @@ const ReviewComponent = ({ values, classes, network }: Props) => {
<Paragraph size="md" color="disabled" noMargin>
{addresses[index]}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', addresses[index], network)} target="_blank">
<Link
className={classes.open}
to={getEtherScanLink('address', addresses[index], network)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block>
@ -143,10 +147,8 @@ const ReviewComponent = ({ values, classes, network }: Props) => {
</Row>
<Row className={classes.info} align="center">
<Paragraph noMargin color="primary" size="md">
{"You're about to create a new Safe."}
</Paragraph>
<Paragraph noMargin color="primary" size="md">
Make sure you have enough ETH in your wallet client to fund this transaction.
You&apos;re about to create a new Safe and will have to confirm a transaction with your currently connected
wallet. Make sure you have ETH in this wallet to fund this transaction.
</Paragraph>
</Row>
</React.Fragment>

View File

@ -5,7 +5,6 @@ import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import { required } from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Row from '~/components/layout/Row'
import { FIELD_NAME } from '~/routes/open/components/fields'
import Paragraph from '~/components/layout/Paragraph'
import OpenPaper from '~/components/Stepper/OpenPaper'
@ -35,10 +34,25 @@ const styles = () => ({
const SafeName = ({ classes }: Props) => (
<React.Fragment>
<Block margin="lg">
<Paragraph noMargin size="md" color="primary">
You are about to create a new Gnosis Safe wallet with one or more owners. First, let&apos;s give your new wallet
a name. This name is only stored locally and will never be shared with Gnosis or any third parties.
</Paragraph>
</Block>
<Block margin="lg" className={classes.root}>
<Field
name={FIELD_NAME}
component={TextField}
type="text"
validate={required}
placeholder="Name of the new Safe"
text="Safe name"
/>
</Block>
<Block margin="lg">
<Paragraph noMargin size="md" color="primary" className={classes.links}>
This setup will create a Safe with one or more owners. Optionally give the Safe a local name. By continuing you
consent with the
By continuing you consent with the
{' '}
<a rel="noopener noreferrer" href="https://safe.gnosis.io/terms" target="_blank">
terms of use
@ -49,35 +63,10 @@ const SafeName = ({ classes }: Props) => (
<a rel="noopener noreferrer" href="https://safe.gnosis.io/privacy" target="_blank">
privacy policy
</a>
.
. Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the
Ethereum blockchain. These funds cannot be accessed by Gnosis at any point.
</Paragraph>
</Block>
<Row margin="md" className={classes.text}>
<Paragraph noMargin className={classes.dot} color="secondary">
&#9679;
</Paragraph>
<Paragraph noMargin size="md" color="primary" weight="bolder">
I understand that my funds are held securely in my Safe. They cannot be accessed by Gnosis.
</Paragraph>
</Row>
<Row margin="md">
<Paragraph noMargin className={classes.dot} color="secondary">
&#9679;
</Paragraph>
<Paragraph noMargin size="md" color="primary" weight="bolder">
My Safe is a smart contract on the Ethereum blockchain.
</Paragraph>
</Row>
<Block margin="lg" className={classes.root}>
<Field
name={FIELD_NAME}
component={TextField}
type="text"
validate={required}
placeholder="Name of the new Safe"
text="Safe name"
/>
</Block>
</React.Fragment>
)

View File

@ -0,0 +1,119 @@
// @flow
import * as React from 'react'
import QrReader from 'react-qr-reader'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import CircularProgress from '@material-ui/core/CircularProgress'
import Paragraph from '~/components/layout/Paragraph'
import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block'
import Row from '~/components/layout/Row'
import Hairline from '~/components/layout/Hairline'
import Col from '~/components/layout/Col'
import Modal from '~/components/Modal'
import { checkWebcam } from './utils'
import { styles } from './style'
const { useEffect, useState } = React
type Props = {
onClose: () => void,
classes: Object,
onScan: Function,
isOpen: boolean,
}
const ScanQRModal = ({
classes, onClose, isOpen, onScan,
}: Props) => {
const [hasWebcam, setHasWebcam] = useState(null)
const scannerRef: Object = React.createRef()
const openImageDialog = () => {
scannerRef.current.openImageDialog()
}
useEffect(() => {
checkWebcam(
() => {
setHasWebcam(true)
},
() => {
setHasWebcam(false)
},
)
}, [])
useEffect(() => {
// this fires only when the hasWebcam changes to false (null > false (user doesn't have webcam)
// , true > false (user switched from webcam to file upload))
// Doesn't fire on re-render
if (hasWebcam === false) {
openImageDialog()
}
}, [hasWebcam])
return (
<Modal title="Receive Tokens" description="Receive Tokens Form" handleClose={onClose} open={isOpen}>
<Row align="center" grow className={classes.heading}>
<Paragraph className={classes.manage} weight="bolder" noMargin>
Scan QR
</Paragraph>
<IconButton onClick={onClose} disableRipple>
<Close className={classes.close} />
</IconButton>
</Row>
<Hairline />
<Col layout="column" middle="xs" className={classes.detailsContainer}>
{hasWebcam === null ? (
<Block align="center" className={classes.loaderContainer}>
<CircularProgress />
</Block>
) : (
<QrReader
ref={scannerRef}
legacyMode={!hasWebcam}
onScan={(data) => {
if (data) onScan(data)
}}
onError={(err) => {
console.error(err)
}}
style={{ width: '400px', height: '400px' }}
/>
)}
</Col>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button
color="secondary"
className={classes.button}
minHeight={42}
minWidth={140}
onClick={onClose}
variant="contained"
>
Close
</Button>
<Button
color="primary"
className={classes.button}
minWidth={140}
minHeight={42}
onClick={() => {
if (hasWebcam) {
setHasWebcam(false)
} else {
openImageDialog()
}
}}
variant="contained"
>
Upload an image
</Button>
</Row>
</Modal>
)
}
export default withStyles(styles)(ScanQRModal)

View File

@ -0,0 +1,35 @@
// @flow
import { lg, sm, background } from '~/theme/variables'
export const styles = () => ({
heading: {
padding: `${sm} ${lg}`,
justifyContent: 'space-between',
maxHeight: '75px',
boxSizing: 'border-box',
},
loaderContainer: {
width: '100%',
height: '100%',
},
manage: {
fontSize: '24px',
},
close: {
height: '35px',
width: '35px',
},
detailsContainer: {
backgroundColor: background,
maxHeight: '420px',
},
buttonRow: {
height: '84px',
justifyContent: 'center',
},
button: {
'&:last-child': {
marginLeft: sm,
},
},
})

View File

@ -0,0 +1,15 @@
// @flow
navigator.getMedia = navigator.getUserMedia // use the proper vendor prefix
|| navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia
|| navigator.msGetUserMedia
export const checkWebcam = (success: Function, err: Function) => navigator.getMedia(
{ video: true },
() => {
success()
},
() => {
err()
},
)

View File

@ -0,0 +1,231 @@
// @flow
import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import InputAdornment from '@material-ui/core/InputAdornment'
import CheckCircle from '@material-ui/icons/CheckCircle'
import MenuItem from '@material-ui/core/MenuItem'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import SelectField from '~/components/forms/SelectField'
import {
required, composeValidators, noErrorsOn, mustBeInteger, minValue,
} from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button'
import Row from '~/components/layout/Row'
import Img from '~/components/layout/Img'
import Col from '~/components/layout/Col'
import {
FIELD_CONFIRMATIONS,
getOwnerNameBy,
getOwnerAddressBy,
getNumOwnersFrom,
} from '~/routes/open/components/fields'
import Paragraph from '~/components/layout/Paragraph'
import OpenPaper from '~/components/Stepper/OpenPaper'
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Hairline from '~/components/layout/Hairline'
import trash from '~/assets/icons/trash.svg'
import QRIcon from '~/assets/icons/qrcode.svg'
import ScanQRModal from './ScanQRModal'
import { getAddressValidators } from './validators'
import { styles } from './style'
type Props = {
classes: Object,
otherAccounts: string[],
errors: Object,
form: Object,
values: Object,
}
const { useState } = React
export const ADD_OWNER_BUTTON = '+ ADD ANOTHER OWNER'
export const calculateValuesAfterRemoving = (index: number, notRemovedOwners: number, values: Object) => {
const initialValues = { ...values }
const numOwnersAfterRemoving = notRemovedOwners - 1
// muevo indices
for (let i = index; i < numOwnersAfterRemoving; i += 1) {
initialValues[getOwnerNameBy(i)] = values[getOwnerNameBy(i + 1)]
initialValues[getOwnerAddressBy(i)] = values[getOwnerAddressBy(i + 1)]
}
if (+values[FIELD_CONFIRMATIONS] === notRemovedOwners) {
initialValues[FIELD_CONFIRMATIONS] = numOwnersAfterRemoving.toString()
}
delete initialValues[getOwnerNameBy(index)]
delete initialValues[getOwnerAddressBy(index)]
return initialValues
}
const SafeOwners = (props: Props) => {
const {
classes, errors, otherAccounts, values, form,
} = props
const validOwners = getNumOwnersFrom(values)
const [numOwners, setNumOwners] = useState<number>(validOwners)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [scanQrForOwnerName, setScanQrForOwnerName] = useState<string | null>(null)
const openQrModal = (ownerName) => {
setScanQrForOwnerName(ownerName)
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
const onRemoveRow = (index: number) => () => {
const initialValues = calculateValuesAfterRemoving(index, numOwners, values)
form.reset(initialValues)
setNumOwners(numOwners - 1)
}
const onAddOwner = () => {
setNumOwners(numOwners + 1)
}
const handleScan = (value) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
form.mutators.setValue(scanQrForOwnerName, scannedAddress)
closeQrModal()
}
return (
<React.Fragment>
<Block className={classes.title}>
<Paragraph noMargin size="md" color="primary">
Specify the owners of the Safe.
</Paragraph>
</Block>
<Hairline />
<Row className={classes.header}>
<Col xs={4}>NAME</Col>
<Col xs={8}>ADDRESS</Col>
</Row>
<Hairline />
<Block margin="md" padding="md">
{[...Array(Number(numOwners))].map((x, index) => {
const addressName = getOwnerAddressBy(index)
return (
<Row key={`owner${index}`} className={classes.owner}>
<Col xs={4}>
<Field
className={classes.name}
name={getOwnerNameBy(index)}
component={TextField}
type="text"
validate={required}
placeholder="Owner Name*"
text="Owner Name"
/>
</Col>
<Col xs={6}>
<Field
name={addressName}
component={TextField}
inputAdornment={
noErrorsOn(addressName, errors) && {
endAdornment: (
<InputAdornment position="end">
<CheckCircle className={classes.check} />
</InputAdornment>
),
}
}
type="text"
validate={getAddressValidators(otherAccounts, index)}
placeholder="Owner Address*"
text="Owner Address"
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
<Img
src={QRIcon}
height={20}
alt="Scan QR"
onClick={() => {
openQrModal(addressName)
}}
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
{index > 0 && <Img src={trash} height={20} alt="Delete" onClick={onRemoveRow(index)} />}
</Col>
</Row>
)
})}
</Block>
<Row align="center" grow className={classes.add} margin="xl">
<Button color="secondary" onClick={onAddOwner} data-testid="add-owner-btn">
<Paragraph weight="bold" size="md" noMargin>
{ADD_OWNER_BUTTON}
</Paragraph>
</Button>
</Row>
<Block margin="md" padding="md" className={classes.owner}>
<Paragraph size="md" color="primary">
Any transaction requires the confirmation of:
</Paragraph>
<Row margin="xl" align="center">
<Col xs={2}>
<Field
name={FIELD_CONFIRMATIONS}
component={SelectField}
validate={composeValidators(required, mustBeInteger, minValue(1))}
data-testid="threshold-select-input"
>
{[...Array(Number(validOwners))].map((x, index) => (
<MenuItem key={`selectOwner${index}`} value={`${index + 1}`}>
{index + 1}
</MenuItem>
))}
</Field>
</Col>
<Col xs={10}>
<Paragraph size="lg" color="primary" noMargin className={classes.owners}>
out of
{' '}
{validOwners}
{' '}
owner(s)
</Paragraph>
</Col>
</Row>
</Block>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
</React.Fragment>
)
}
const SafeOwnersForm = withStyles(styles)(SafeOwners)
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => (
<React.Fragment>
<OpenPaper controls={controls} padding={false}>
<SafeOwnersForm
otherAccounts={getAccountsFrom(values)}
errors={errors}
form={form}
updateInitialProps={updateInitialProps}
values={values}
/>
</OpenPaper>
</React.Fragment>
)
export default SafeOwnersPage

View File

@ -0,0 +1,44 @@
// @flow
import { md, lg, sm } from '~/theme/variables'
export const styles = () => ({
root: {
display: 'flex',
},
title: {
padding: `${md} ${lg}`,
},
owner: {
padding: `0 ${lg}`,
marginTop: '12px',
'&:first-child': {
marginTop: 0,
},
},
header: {
padding: `${sm} ${lg}`,
},
name: {
marginRight: `${sm}`,
},
trash: {
top: '5px',
},
add: {
justifyContent: 'center',
},
check: {
color: '#03AE60',
height: '20px',
},
remove: {
height: '56px',
maxWidth: '50px',
'&:hover': {
cursor: 'pointer',
},
},
owners: {
paddingLeft: md,
},
})

View File

@ -0,0 +1,17 @@
// @flow
import {
required,
composeValidators,
uniqueAddress,
mustBeEthereumAddress,
} from '~/components/forms/validator'
export const getAddressValidators = (addresses: string[], position: number) => {
// thanks Rich Harris
// https://twitter.com/Rich_Harris/status/1125850391155965952
const copy = addresses.slice()
copy[position] = copy[copy.length - 1]
copy.pop()
return composeValidators(required, mustBeEthereumAddress, uniqueAddress(copy))
}

View File

@ -1,212 +0,0 @@
// @flow
import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import InputAdornment from '@material-ui/core/InputAdornment'
import CheckCircle from '@material-ui/icons/CheckCircle'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import {
required,
composeValidators,
uniqueAddress,
mustBeEthereumAddress,
noErrorsOn,
} from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Button from '~/components/layout/Button'
import Row from '~/components/layout/Row'
import Img from '~/components/layout/Img'
import Col from '~/components/layout/Col'
import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
import Paragraph from '~/components/layout/Paragraph'
import OpenPaper from '~/components/Stepper/OpenPaper'
import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Hairline from '~/components/layout/Hairline'
import { md, lg, sm } from '~/theme/variables'
import trash from '~/assets/icons/trash.svg'
type Props = {
classes: Object,
otherAccounts: string[],
errors: Object,
values: Object,
updateInitialProps: (initialValues: Object) => void,
}
type State = {
numOwners: number,
}
const styles = () => ({
root: {
display: 'flex',
},
title: {
padding: `${md} ${lg}`,
},
owner: {
padding: `0 ${lg}`,
},
header: {
padding: `${sm} ${lg}`,
},
name: {
marginRight: `${sm}`,
},
trash: {
top: '5px',
},
add: {
justifyContent: 'center',
},
check: {
color: '#03AE60',
height: '20px',
},
remove: {
height: '56px',
marginTop: '12px',
maxWidth: '50px',
'&:hover': {
cursor: 'pointer',
},
},
})
const getAddressValidators = (addresses: string[], position: number) => {
// thanks Rich Harris
// https://twitter.com/Rich_Harris/status/1125850391155965952
const copy = addresses.slice()
copy[position] = copy[copy.length - 1]
copy.pop()
return composeValidators(required, mustBeEthereumAddress, uniqueAddress(copy))
}
export const ADD_OWNER_BUTTON = '+ ADD ANOTHER OWNER'
export const calculateValuesAfterRemoving = (index: number, notRemovedOwners: number, values: Object) => {
const initialValues = { ...values }
const numOwnersAfterRemoving = notRemovedOwners - 1
// muevo indices
for (let i = index; i < numOwnersAfterRemoving; i += 1) {
initialValues[getOwnerNameBy(i)] = values[getOwnerNameBy(i + 1)]
initialValues[getOwnerAddressBy(i)] = values[getOwnerAddressBy(i + 1)]
}
delete initialValues[getOwnerNameBy(numOwnersAfterRemoving)]
delete initialValues[getOwnerAddressBy(numOwnersAfterRemoving)]
return initialValues
}
class SafeOwners extends React.PureComponent<Props, State> {
state = {
numOwners: 1,
}
onRemoveRow = (index: number) => () => {
const { values, updateInitialProps } = this.props
const { numOwners } = this.state
const initialValues = calculateValuesAfterRemoving(index, numOwners, values)
updateInitialProps(initialValues)
this.setState(state => ({
numOwners: state.numOwners - 1,
}))
}
onAddOwner = () => {
this.setState(state => ({
numOwners: state.numOwners + 1,
}))
}
render() {
const { classes, errors, otherAccounts } = this.props
const { numOwners } = this.state
return (
<React.Fragment>
<Block className={classes.title}>
<Paragraph noMargin size="md" color="primary">
Specify the owners of the Safe.
</Paragraph>
</Block>
<Hairline />
<Row className={classes.header}>
<Col xs={4}>NAME</Col>
<Col xs={8}>ADDRESS</Col>
</Row>
<Hairline />
<Block margin="md" padding="md">
{[...Array(Number(numOwners))].map((x, index) => {
const addressName = getOwnerAddressBy(index)
return (
<Row key={`owner${index}`} className={classes.owner}>
<Col xs={4}>
<Field
className={classes.name}
name={getOwnerNameBy(index)}
component={TextField}
type="text"
validate={required}
placeholder="Owner Name*"
text="Owner Name"
/>
</Col>
<Col xs={7}>
<Field
name={addressName}
component={TextField}
inputAdornment={
noErrorsOn(addressName, errors) && {
endAdornment: (
<InputAdornment position="end">
<CheckCircle className={classes.check} />
</InputAdornment>
),
}
}
type="text"
validate={getAddressValidators(otherAccounts, index)}
placeholder="Owner Address*"
text="Owner Address"
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes.remove}>
{index > 0 && <Img src={trash} height={20} alt="Delete" onClick={this.onRemoveRow(index)} />}
</Col>
</Row>
)
})}
</Block>
<Row align="center" grow className={classes.add} margin="xl">
<Button color="secondary" onClick={this.onAddOwner} data-testid="add-owner-btn">
<Paragraph weight="bold" size="md" noMargin>
{ADD_OWNER_BUTTON}
</Paragraph>
</Button>
</Row>
</React.Fragment>
)
}
}
const SafeOwnersForm = withStyles(styles)(SafeOwners)
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors }: Object) => (
<React.Fragment>
<OpenPaper controls={controls} padding={false}>
<SafeOwnersForm
otherAccounts={getAccountsFrom(values)}
errors={errors}
updateInitialProps={updateInitialProps}
values={values}
/>
</OpenPaper>
</React.Fragment>
)
export default SafeOwnersPage

View File

@ -1,91 +0,0 @@
// @flow
import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import MenuItem from '@material-ui/core/MenuItem'
import Field from '~/components/forms/Field'
import SelectField from '~/components/forms/SelectField'
import {
composeValidators, minValue, mustBeInteger, required,
} from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
import Paragraph from '~/components/layout/Paragraph'
import OpenPaper from '~/components/Stepper/OpenPaper'
import { FIELD_CONFIRMATIONS, getNumOwnersFrom } from '~/routes/open/components/fields'
import { md } from '~/theme/variables'
type Props = {
classes: Object,
values: Object,
}
const styles = () => ({
owners: {
paddingLeft: md,
},
})
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
export const safeFieldsValidation = (values: Object) => {
const errors = {}
const numOwners = getNumOwnersFrom(values)
if (numOwners < Number.parseInt(values[FIELD_CONFIRMATIONS], 10)) {
errors[FIELD_CONFIRMATIONS] = CONFIRMATIONS_ERROR
}
return errors
}
const SafeThreshold = ({ classes, values }: Props) => {
const numOwners = getNumOwnersFrom(values)
return (
<React.Fragment>
<Block margin="xs">
<Paragraph noMargin size="md" color="primary" weight="bolder">
Any transaction requires the confirmation of:
</Paragraph>
</Block>
<Row margin="xl" align="center">
<Col xs={2}>
<Field
name={FIELD_CONFIRMATIONS}
component={SelectField}
validate={composeValidators(required, mustBeInteger, minValue(1))}
data-testid="threshold-select-input"
>
{[...Array(Number(numOwners))].map((x, index) => (
<MenuItem key={`selectOwner${index}`} value={`${index + 1}`}>
{index + 1}
</MenuItem>
))}
</Field>
</Col>
<Col xs={10}>
<Paragraph size="lg" color="primary" noMargin className={classes.owners}>
out of
{' '}
{numOwners}
{' '}
owner(s)
</Paragraph>
</Col>
</Row>
</React.Fragment>
)
}
const SafeThresholdForm = withStyles(styles)(SafeThreshold)
const SafeOwnersPage = () => (controls: React.Node, { values }: Object) => (
<React.Fragment>
<OpenPaper controls={controls} container={450}>
<SafeThresholdForm values={values} />
</OpenPaper>
</React.Fragment>
)
export default SafeOwnersPage

View File

@ -9,7 +9,7 @@ export const getOwnerAddressBy = (index: number) => `owner${index}Address`
export const getNumOwnersFrom = (values: Object) => {
const accounts = Object.keys(values)
.sort()
.filter(key => /^owner\d+Name$/.test(key))
.filter(key => /^owner\d+Address$/.test(key) && !!values[key])
return accounts.length
}

View File

@ -4,8 +4,8 @@ import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Link from '~/components/layout/Link'
import QRCode from 'qrcode.react'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph'
import Identicon from '~/components/Identicon'
import Button from '~/components/layout/Button'

View File

@ -1,39 +0,0 @@
// @flow
import * as React from 'react'
import CircularProgress from '@material-ui/core/CircularProgress'
import Block from '~/components/layout/Block'
import Bold from '~/components/layout/Bold'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Heading from '~/components/layout/Heading'
import Paragraph from '~/components/layout/Paragraph'
import { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from '~/routes/safe/components/SendToken/SendTokenForm/index'
type FormProps = {
values: Object,
submitting: boolean,
}
type Props = {
symbol: string,
}
const spinnerStyle = {
minHeight: '50px',
}
const ReviewTx = ({ symbol }: Props) => (controls: React.Node, { values, submitting }: FormProps) => (
<OpenPaper controls={controls}>
<Heading tag="h2">Review the move token funds</Heading>
<Paragraph align="left">
<Bold>Destination: </Bold>
{' '}
{values[TKN_DESTINATION_PARAM]}
</Paragraph>
<Paragraph align="left">
<Bold>{`Amount to transfer: ${values[TKN_VALUE_PARAM]} ${symbol}`}</Bold>
</Paragraph>
<Block style={spinnerStyle}>{submitting && <CircularProgress size={50} />}</Block>
</OpenPaper>
)
export default ReviewTx

View File

@ -1,53 +0,0 @@
// @flow
import * as React from 'react'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import {
composeValidators, inLimit, mustBeFloat, required, greaterThan, mustBeEthereumAddress,
} from '~/components/forms/validator'
import Block from '~/components/layout/Block'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Heading from '~/components/layout/Heading'
export const CONFIRMATIONS_ERROR = 'Number of confirmations can not be higher than the number of owners'
export const TKN_DESTINATION_PARAM = 'tknDestination'
export const TKN_VALUE_PARAM = 'tknValue'
type Props = {
funds: string,
symbol: string,
}
const SendTokenForm = ({ funds, symbol }: Props) => (controls: React.Node) => (
<OpenPaper controls={controls}>
<Heading tag="h2" margin="lg">
Send tokens Transaction
</Heading>
<Heading tag="h4" margin="lg">
{`Available tokens: ${funds} ${symbol}`}
</Heading>
<Block margin="md">
<Field
name={TKN_DESTINATION_PARAM}
component={TextField}
type="text"
validate={composeValidators(required, mustBeEthereumAddress)}
placeholder="Destination*"
text="Destination"
/>
</Block>
<Block margin="md">
<Field
name={TKN_VALUE_PARAM}
component={TextField}
type="text"
validate={composeValidators(required, mustBeFloat, greaterThan(0), inLimit(Number(funds), 0, 'available balance', symbol))}
placeholder="Amount of tokens*"
text="Amount of Tokens"
/>
</Block>
</OpenPaper>
)
export default SendTokenForm

View File

@ -1,10 +0,0 @@
// @flow
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
export type Actions = {
fetchTransactions: typeof fetchTransactions,
}
export default {
fetchTransactions,
}

View File

@ -1,119 +0,0 @@
// @flow
import * as React from 'react'
import { BigNumber } from 'bignumber.js'
import { connect } from 'react-redux'
import Stepper from '~/components/Stepper'
import { sleep } from '~/utils/timer'
import { type Safe } from '~/routes/safe/store/models/safe'
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
import { type Token } from '~/logic/tokens/store/model/token'
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { toNative } from '~/logic/wallets/tokens'
import { createTransaction } from '~/logic/safe/safeFrontendOperations'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import actions, { type Actions } from './actions'
import selector, { type SelectorProps } from './selector'
import SendTokenForm, { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from './SendTokenForm'
import ReviewTx from './ReviewTx'
const getSteps = () => ['Fill Move Token form', 'Review Move Token form']
type Props = SelectorProps &
Actions & {
safe: Safe,
token: Token,
onReset: () => void,
}
type State = {
done: boolean,
}
export const SEE_TXS_BUTTON_TEXT = 'VISIT TXS'
const getTransferData = async (tokenAddress: string, to: string, amount: BigNumber) => {
const StandardToken = await getStandardTokenContract()
const myToken = await StandardToken.at(tokenAddress)
return myToken.contract.transfer(to, amount).encodeABI()
}
const processTokenTransfer = async (safe: Safe, token: Token, to: string, amount: string, userAddress: string) => {
const safeAddress = safe.get('address')
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const nonce = await gnosisSafe.nonce()
const symbol = token.get('symbol')
const name = `Send ${amount} ${symbol} to ${to}`
const value = isEther(symbol) ? amount : '0'
const tokenAddress = token.get('address')
const destination = isEther(symbol) ? to : tokenAddress
const data = isEther(symbol)
? EMPTY_DATA
: await getTransferData(tokenAddress, to, toNative(amount, token.get('decimals')))
return createTransaction(safe, name, destination, value, nonce, userAddress, data)
}
class SendToken extends React.Component<Props, State> {
state = {
done: false,
}
onTransaction = async (values: Object) => {
try {
const {
safe, token, userAddress, fetchTransactions,
} = this.props
const amount = values[TKN_VALUE_PARAM]
const destination = values[TKN_DESTINATION_PARAM]
await processTokenTransfer(safe, token, destination, amount, userAddress)
await sleep(1500)
fetchTransactions(safe.get('address'))
this.setState({ done: true })
} catch (error) {
this.setState({ done: false })
// eslint-disable-next-line
console.log('Error while moving ERC20 token funds ' + error)
}
}
onReset = () => {
const { onReset } = this.props
this.setState({ done: false })
onReset() // This is for show the TX list component
}
render() {
const { done } = this.state
const { token } = this.props
const steps = getSteps()
const finishedButton = <Stepper.FinishButton title={SEE_TXS_BUTTON_TEXT} />
const symbol = token.get('symbol')
return (
<React.Fragment>
<Stepper
finishedTransaction={done}
finishedButton={finishedButton}
onSubmit={this.onTransaction}
steps={steps}
onReset={this.onReset}
>
<Stepper.Page funds={token.get('funds')} symbol={symbol}>
{SendTokenForm}
</Stepper.Page>
<Stepper.Page symbol={symbol}>{ReviewTx}</Stepper.Page>
</Stepper>
</React.Fragment>
)
}
}
export default connect(
selector,
actions,
)(SendToken)

View File

@ -1,11 +0,0 @@
// @flow
import { createStructuredSelector } from 'reselect'
import { userAccountSelector } from '~/logic/wallets/store/selectors'
export type SelectorProps = {
userAddress: typeof userAccountSelector,
}
export default createStructuredSelector<Object, *>({
userAddress: userAccountSelector,
})

View File

@ -1,34 +0,0 @@
// @flow
import * as React from 'react'
import CircularProgress from '@material-ui/core/CircularProgress'
import Block from '~/components/layout/Block'
import Bold from '~/components/layout/Bold'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Heading from '~/components/layout/Heading'
import Paragraph from '~/components/layout/Paragraph'
import { THRESHOLD_PARAM } from '~/routes/safe/components/Threshold/ThresholdForm'
type FormProps = {
values: Object,
submitting: boolean,
}
const spinnerStyle = {
minHeight: '50px',
}
const Review = () => (controls: React.Node, { values, submitting }: FormProps) => (
<OpenPaper controls={controls}>
<Heading tag="h2">Review the Threshold operation</Heading>
<Paragraph align="left">
<Bold>The new threshold will be: </Bold>
{' '}
{values[THRESHOLD_PARAM]}
</Paragraph>
<Block style={spinnerStyle}>
{ submitting && <CircularProgress size={50} /> }
</Block>
</OpenPaper>
)
export default Review

View File

@ -1,46 +0,0 @@
// @flow
import * as React from 'react'
import Block from '~/components/layout/Block'
import Heading from '~/components/layout/Heading'
import OpenPaper from '~/components/Stepper/OpenPaper'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import {
composeValidators, minValue, maxValue, mustBeInteger, required,
} from '~/components/forms/validator'
import { type Safe } from '~/routes/safe/store/models/safe'
export const THRESHOLD_PARAM = 'threshold'
type ThresholdProps = {
numOwners: number,
safe: Safe,
}
const ThresholdForm = ({ numOwners, safe }: ThresholdProps) => (controls: React.Node) => (
<OpenPaper controls={controls}>
<Heading tag="h2" margin="lg">
{'Change safe\'s threshold'}
</Heading>
<Heading tag="h4" margin="lg">
{`Safe's owners: ${numOwners} and Safe's threshold: ${safe.get('threshold')}`}
</Heading>
<Block margin="md">
<Field
name={THRESHOLD_PARAM}
component={TextField}
type="text"
validate={composeValidators(
required,
mustBeInteger,
minValue(1),
maxValue(numOwners),
)}
placeholder="New threshold"
text="Safe's threshold"
/>
</Block>
</OpenPaper>
)
export default ThresholdForm

View File

@ -1,10 +0,0 @@
// @flow
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
export type Actions = {
fetchTransactions: typeof fetchTransactions,
}
export default {
fetchTransactions,
}

View File

@ -1,86 +0,0 @@
// @flow
import * as React from 'react'
import Stepper from '~/components/Stepper'
import { connect } from 'react-redux'
import { createTransaction } from '~/logic/safe/safeFrontendOperations'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { type Safe } from '~/routes/safe/store/models/safe'
import ThresholdForm, { THRESHOLD_PARAM } from './ThresholdForm'
import selector, { type SelectorProps } from './selector'
import actions, { type Actions } from './actions'
import Review from './Review'
type Props = SelectorProps & Actions & {
numOwners: number,
safe: Safe,
onReset: () => void,
}
const getSteps = () => [
'Fill Change threshold Form', 'Review change threshold operation',
]
type State = {
done: boolean,
}
export const CHANGE_THRESHOLD_RESET_BUTTON_TEXT = 'SEE TXs'
class Threshold extends React.PureComponent<Props, State> {
state = {
done: false,
}
onThreshold = async (values: Object) => {
try {
const { safe, userAddress, fetchTransactions } = this.props // , fetchThreshold } = this.props
const newThreshold = values[THRESHOLD_PARAM]
const safeAddress = safe.get('address')
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const nonce = await gnosisSafe.nonce()
const data = gnosisSafe.contract.changeThreshold(newThreshold).encodeABI()
await createTransaction(safe, `Change Safe's threshold [${nonce}]`, safeAddress, '0', nonce, userAddress, data)
await fetchTransactions(safeAddress)
this.setState({ done: true })
} catch (error) {
this.setState({ done: false })
// eslint-disable-next-line
console.log('Error while changing threshold ' + error)
}
}
onReset = () => {
const { onReset } = this.props
this.setState({ done: false })
onReset()
}
render() {
const { numOwners, safe } = this.props
const { done } = this.state
const steps = getSteps()
const finishedButton = <Stepper.FinishButton title={CHANGE_THRESHOLD_RESET_BUTTON_TEXT} />
return (
<React.Fragment>
<Stepper
finishedTransaction={done}
finishedButton={finishedButton}
onSubmit={this.onThreshold}
steps={steps}
onReset={this.onReset}
>
<Stepper.Page numOwners={numOwners} safe={safe}>
{ ThresholdForm }
</Stepper.Page>
<Stepper.Page>
{ Review }
</Stepper.Page>
</Stepper>
</React.Fragment>
)
}
}
export default connect(selector, actions)(Threshold)

View File

@ -1,11 +0,0 @@
// @flow
import { createStructuredSelector } from 'reselect'
import { userAccountSelector } from '~/logic/wallets/store/selectors'
export type SelectorProps = {
userAddress: typeof userAccountSelector,
}
export default createStructuredSelector<Object, *>({
userAddress: userAccountSelector,
})

View File

@ -2,9 +2,6 @@
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { getWeb3 } from '~/logic/wallets/getWeb3'
const web3 = getWeb3()
const { toBN, fromWei } = web3.utils
type DecodedTxData = {
recipient: string,
value?: string,
@ -16,6 +13,9 @@ type DecodedTxData = {
}
export const getTxData = (tx: Transaction): DecodedTxData => {
const web3 = getWeb3()
const { toBN, fromWei } = web3.utils
const txData = {}
if (tx.isTokenTransfer && tx.decodedParams) {

View File

@ -14,9 +14,6 @@ export const TX_TABLE_STATUS_ID = 'status'
export const TX_TABLE_RAW_TX_ID = 'tx'
export const TX_TABLE_EXPAND_ICON = 'expand'
const web3 = getWeb3()
const { toBN, fromWei } = web3.utils
type TxData = {
nonce: number,
type: string,
@ -29,6 +26,9 @@ type TxData = {
export const formatDate = (date: Date): string => format(date, 'MMM D, YYYY - HH:mm:ss')
export const getTxAmount = (tx: Transaction) => {
const web3 = getWeb3()
const { toBN, fromWei } = web3.utils
let txAmount = 'n/a'
if (tx.isTokenTransfer && tx.decodedParams) {

View File

@ -4,7 +4,7 @@ import { type Store } from 'redux'
import { render, fireEvent, cleanup } from '@testing-library/react'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import { ADD_OWNER_BUTTON } from '~/routes/open/components/SafeOwnersForm'
import { ADD_OWNER_BUTTON } from '~/routes/open/components/SafeOwnersConfirmationsForm'
import Open from '~/routes/open/container/Open'
import { aNewStore, history, type GlobalState } from '~/store'
import { sleep } from '~/utils/timer'
@ -80,14 +80,12 @@ const deploySafe = async (createSafeForm: any, threshold: number, numOwners: num
fireEvent.change(ownerNameInput, { target: { value: `Owner ${i + 1}` } })
fireEvent.change(ownerAddressInput, { target: { value: accounts[i] } })
}
fireEvent.submit(form)
await sleep(600)
// Fill Threshold
// The test is fragile here, MUI select btn is hard to find
const thresholdSelect = createSafeForm.getAllByRole('button')[1]
const thresholdSelect = createSafeForm.getAllByRole('button')[2]
fireEvent.click(thresholdSelect)
const thresholdOptions = createSafeForm.getAllByRole('option')
fireEvent.click(thresholdOptions[numOwners - 1])
fireEvent.submit(form)

334
yarn.lock
View File

@ -2,16 +2,16 @@
# yarn lockfile v1
"@babel/cli@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.5.0.tgz#f403c930692e28ecfa3bf02a9e7562b474f38271"
integrity sha512-qNH55fWbKrEsCwID+Qc/3JDPnsSGpIIiMDbppnR8Z6PxLAqMQCFNqBctkIkBrMH49Nx+qqVTrHRWUR+ho2k+qQ==
"@babel/cli@7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.5.5.tgz#bdb6d9169e93e241a08f5f7b0265195bf38ef5ec"
integrity sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==
dependencies:
commander "^2.8.1"
convert-source-map "^1.1.0"
fs-readdir-recursive "^1.1.0"
glob "^7.0.0"
lodash "^4.17.11"
lodash "^4.17.13"
mkdirp "^0.5.1"
output-file-sync "^2.0.0"
slash "^2.0.0"
@ -53,27 +53,7 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/core@7.5.4":
version "7.5.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.4.tgz#4c32df7ad5a58e9ea27ad025c11276324e0b4ddd"
integrity sha512-+DaeBEpYq6b2+ZmHx3tHspC+ZRflrvLqwfv8E3hNr5LVQoyBnL8RPKSBCg+rK2W2My9PWlujBiqd0ZPsR9Q6zQ==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/generator" "^7.5.0"
"@babel/helpers" "^7.5.4"
"@babel/parser" "^7.5.0"
"@babel/template" "^7.4.4"
"@babel/traverse" "^7.5.0"
"@babel/types" "^7.5.0"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
lodash "^4.17.11"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.4.5":
"@babel/core@7.5.5", "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.4.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30"
integrity sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==
@ -93,7 +73,7 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/generator@^7.4.0", "@babel/generator@^7.5.0", "@babel/generator@^7.5.5":
"@babel/generator@^7.4.0", "@babel/generator@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf"
integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==
@ -136,7 +116,7 @@
"@babel/traverse" "^7.4.4"
"@babel/types" "^7.4.4"
"@babel/helper-create-class-features-plugin@^7.4.0", "@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.0", "@babel/helper-create-class-features-plugin@^7.5.5":
"@babel/helper-create-class-features-plugin@^7.4.0", "@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz#401f302c8ddbc0edd36f7c6b2887d8fa1122e5a4"
integrity sha512-ZsxkyYiRA7Bg+ZTRpPvB6AbOFKTFFK4LrvTet8lInm0V468MWCaSYJE+I7v2z2r8KNLtYiV+K5kTCnR7dvyZjg==
@ -279,7 +259,7 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.2.0"
"@babel/helpers@^7.4.3", "@babel/helpers@^7.5.4", "@babel/helpers@^7.5.5":
"@babel/helpers@^7.4.3", "@babel/helpers@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e"
integrity sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==
@ -297,7 +277,7 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.0", "@babel/parser@^7.5.5":
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b"
integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==
@ -319,15 +299,7 @@
"@babel/helper-create-class-features-plugin" "^7.4.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-class-properties@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.0.tgz#5bc6a0537d286fcb4fd4e89975adbca334987007"
integrity sha512-9L/JfPCT+kShiiTTzcnBJ8cOwdKVmlC1RcCf9F0F9tERVrM4iWtWnXtjWCRqNm2la2BxO1MPArWNsU9zsSJWSQ==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.5.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-class-properties@^7.3.3":
"@babel/plugin-proposal-class-properties@7.5.5", "@babel/plugin-proposal-class-properties@^7.3.3":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz#a974cfae1e37c3110e71f3c6a2e48b8e71958cd4"
integrity sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==
@ -442,7 +414,7 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
"@babel/plugin-proposal-object-rest-spread@^7.3.2", "@babel/plugin-proposal-object-rest-spread@^7.4.3", "@babel/plugin-proposal-object-rest-spread@^7.5.4", "@babel/plugin-proposal-object-rest-spread@^7.5.5":
"@babel/plugin-proposal-object-rest-spread@^7.3.2", "@babel/plugin-proposal-object-rest-spread@^7.4.3", "@babel/plugin-proposal-object-rest-spread@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58"
integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==
@ -661,7 +633,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-block-scoping@^7.4.0", "@babel/plugin-transform-block-scoping@^7.4.4", "@babel/plugin-transform-block-scoping@^7.5.5":
"@babel/plugin-transform-block-scoping@^7.4.0", "@babel/plugin-transform-block-scoping@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz#a35f395e5402822f10d2119f6f8e045e3639a2ce"
integrity sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==
@ -683,7 +655,7 @@
"@babel/helper-split-export-declaration" "^7.4.0"
globals "^11.1.0"
"@babel/plugin-transform-classes@^7.4.3", "@babel/plugin-transform-classes@^7.4.4", "@babel/plugin-transform-classes@^7.5.5":
"@babel/plugin-transform-classes@^7.4.3", "@babel/plugin-transform-classes@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9"
integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==
@ -1050,63 +1022,7 @@
js-levenshtein "^1.1.3"
semver "^5.5.0"
"@babel/preset-env@7.5.4":
version "7.5.4"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.4.tgz#64bc15041a3cbb0798930319917e70fcca57713d"
integrity sha512-hFnFnouyRNiH1rL8YkX1ANCNAUVC8Djwdqfev8i1415tnAG+7hlA5zhZ0Q/3Q5gkop4HioIPbCEWAalqcbxRoQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-async-generator-functions" "^7.2.0"
"@babel/plugin-proposal-dynamic-import" "^7.5.0"
"@babel/plugin-proposal-json-strings" "^7.2.0"
"@babel/plugin-proposal-object-rest-spread" "^7.5.4"
"@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
"@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
"@babel/plugin-syntax-async-generators" "^7.2.0"
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-transform-arrow-functions" "^7.2.0"
"@babel/plugin-transform-async-to-generator" "^7.5.0"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0"
"@babel/plugin-transform-block-scoping" "^7.4.4"
"@babel/plugin-transform-classes" "^7.4.4"
"@babel/plugin-transform-computed-properties" "^7.2.0"
"@babel/plugin-transform-destructuring" "^7.5.0"
"@babel/plugin-transform-dotall-regex" "^7.4.4"
"@babel/plugin-transform-duplicate-keys" "^7.5.0"
"@babel/plugin-transform-exponentiation-operator" "^7.2.0"
"@babel/plugin-transform-for-of" "^7.4.4"
"@babel/plugin-transform-function-name" "^7.4.4"
"@babel/plugin-transform-literals" "^7.2.0"
"@babel/plugin-transform-member-expression-literals" "^7.2.0"
"@babel/plugin-transform-modules-amd" "^7.5.0"
"@babel/plugin-transform-modules-commonjs" "^7.5.0"
"@babel/plugin-transform-modules-systemjs" "^7.5.0"
"@babel/plugin-transform-modules-umd" "^7.2.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5"
"@babel/plugin-transform-new-target" "^7.4.4"
"@babel/plugin-transform-object-super" "^7.2.0"
"@babel/plugin-transform-parameters" "^7.4.4"
"@babel/plugin-transform-property-literals" "^7.2.0"
"@babel/plugin-transform-regenerator" "^7.4.5"
"@babel/plugin-transform-reserved-words" "^7.2.0"
"@babel/plugin-transform-shorthand-properties" "^7.2.0"
"@babel/plugin-transform-spread" "^7.2.0"
"@babel/plugin-transform-sticky-regex" "^7.2.0"
"@babel/plugin-transform-template-literals" "^7.4.4"
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
"@babel/plugin-transform-unicode-regex" "^7.4.4"
"@babel/types" "^7.5.0"
browserslist "^4.6.0"
core-js-compat "^3.1.1"
invariant "^2.2.2"
js-levenshtein "^1.1.3"
semver "^5.5.0"
"@babel/preset-env@^7.4.5":
"@babel/preset-env@7.5.5", "@babel/preset-env@^7.4.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.5.tgz#bc470b53acaa48df4b8db24a570d6da1fef53c9a"
integrity sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==
@ -1227,7 +1143,7 @@
"@babel/parser" "^7.4.4"
"@babel/types" "^7.4.4"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.0", "@babel/traverse@^7.5.5":
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb"
integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==
@ -1242,7 +1158,7 @@
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.0", "@babel/types@^7.5.5":
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
integrity sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==
@ -1573,10 +1489,10 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^12.0.9"
"@material-ui/core@4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.2.0.tgz#fd57352c63a50bc28d8b0d61a779a55aba84f9c4"
integrity sha512-kqwoCMpGaj3zJedihUuVZWjISh+T72KAXOwgk6VKNf+APMTB8yLByLSgSLDhXsliRBO/9Pda/0g/KzGY7R+irQ==
"@material-ui/core@4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.2.1.tgz#18255c01d039ff856bfdb2f955fec6c9ae64a464"
integrity sha512-hasPQUFAb9OxKng7UX2+SjUWtVZbnkVJ/jHZWXTivVcU+UzvNIpA9AyRRQvZ8SPV6swP/HD2VzUBzoMEeRR6wg==
dependencies:
"@babel/runtime" "^7.2.0"
"@material-ui/styles" "^4.2.0"
@ -1585,8 +1501,8 @@
"@material-ui/utils" "^4.1.0"
"@types/react-transition-group" "^2.0.16"
clsx "^1.0.2"
convert-css-length "^2.0.0"
deepmerge "^3.0.0"
convert-css-length "^2.0.1"
deepmerge "^4.0.0"
hoist-non-react-statics "^3.2.1"
is-plain-object "^3.0.0"
normalize-scroll-left "^0.2.0"
@ -4305,14 +4221,14 @@ bignumber.js@^7.2.1:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f"
integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==
"bignumber.js@git+https://github.com/debris/bignumber.js#master":
version "2.0.7"
resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9"
"bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2":
version "2.0.7"
resolved "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2"
"bignumber.js@git+https://github.com/debris/bignumber.js.git#master":
version "2.0.7"
resolved "git+https://github.com/debris/bignumber.js.git#c7a38de919ed75e6fb6ba38051986e294b328df9"
"bignumber.js@git+https://github.com/frozeman/bignumber.js-nolookahead.git":
version "2.0.7"
resolved "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934"
@ -5452,7 +5368,7 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
convert-css-length@^2.0.0:
convert-css-length@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/convert-css-length/-/convert-css-length-2.0.1.tgz#90a76bde5bfd24d72881a5b45d02249b2c1d257c"
integrity sha512-iGpbcvhLPRKUbBc0Quxx7w/bV14AC3ItuBEGMahA5WTYqB8lq9jH0kTXFheCBASsYnqeMFZhiTruNxr1N59Axg==
@ -5731,10 +5647,10 @@ css-in-js-utils@^2.0.0:
hyphenate-style-name "^1.0.2"
isobject "^3.0.1"
css-loader@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.0.0.tgz#bdd48a4921eefedf1f0a55266585944d4e5efc63"
integrity sha512-WR6KZuCkFbnMhRrGPlkwAA7SSCtwqPwpyXJAPhotYkYsc0mKU9n/fu5wufy4jl2WhBw9Ia8gUQMIp/1w98DuPw==
css-loader@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.1.0.tgz#6f008b993b8ce812e6bab57f3cbfdc7a7cf28685"
integrity sha512-MuL8WsF/KSrHCBCYaozBKlx+r7vIfUaDTEreo7wR7Vv3J6N0z6fqWjRk3e/6wjneitXN1r/Y9FTK1psYNOBdJQ==
dependencies:
camelcase "^5.3.1"
cssesc "^3.0.0"
@ -5747,7 +5663,7 @@ css-loader@3.0.0:
postcss-modules-scope "^2.1.0"
postcss-modules-values "^3.0.0"
postcss-value-parser "^4.0.0"
schema-utils "^1.0.0"
schema-utils "^2.0.0"
css-loader@^2.1.1:
version "2.1.1"
@ -6118,11 +6034,6 @@ deep-object-diff@^1.1.0:
resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a"
integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw==
deepmerge@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7"
integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==
deepmerge@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09"
@ -6828,17 +6739,17 @@ eslint-module-utils@^2.4.0:
debug "^2.6.8"
pkg-dir "^2.0.0"
eslint-plugin-flowtype@3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.11.1.tgz#1aae15a10dbcd5aecc89897f810f2e9fcc18a5e3"
integrity sha512-4NiaaGZuz9iEGRTK8j4lkA/scibOXSYaYoHbsTtgLOxxqQCkbWV3xt8ETqILKg7DAYDqB69z1H5U71UmtdF9hw==
eslint-plugin-flowtype@3.12.1:
version "3.12.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.12.1.tgz#b673c716b578c9aa66887feef3bc146f8cbe1c21"
integrity sha512-NZqf5iRgsfHOC31HQdtX2pvzCi0n/j9pB+L7Cf9QtuYxpx0i2wObT+R3rPKhQK4KtEDzGuzPYVf75j4eg+s9ZQ==
dependencies:
lodash "^4.17.11"
eslint-plugin-import@2.18.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.0.tgz#7a5ba8d32622fb35eb9c8db195c2090bd18a3678"
integrity sha512-PZpAEC4gj/6DEMMoU2Df01C5c50r7zdGIN52Yfi7CvvWaYssG7Jt5R9nFG5gmqodxNOz9vQS87xk6Izdtpdrig==
eslint-plugin-import@2.18.1:
version "2.18.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.1.tgz#2e4f571d13839543992ad626a18c0edffde9626b"
integrity sha512-YEESFKOcMIXJTosb5YaepqVhQHGMb8dxkgov560GqMDP/658U5vk6FeVSR7xXLeYkPc7xPYy+uAoiYE/bKMphA==
dependencies:
array-includes "^3.0.3"
contains-path "^0.1.0"
@ -6847,15 +6758,15 @@ eslint-plugin-import@2.18.0:
eslint-import-resolver-node "^0.3.2"
eslint-module-utils "^2.4.0"
has "^1.0.3"
lodash "^4.17.11"
minimatch "^3.0.4"
object.values "^1.1.0"
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-jest@22.9.0:
version "22.9.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.9.0.tgz#2573dbcb4f1066b96a6e6d3b9aa439c80b28975a"
integrity sha512-V89BUiwf76FHlhj1mlNhNyvpzTy8VbWCh2RZpKYz/XDSl/pcuwFiE/LMt7r3q1sRKygzEMjbYeDob8MMuvakXg==
eslint-plugin-jest@22.11.1:
version "22.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.11.1.tgz#04b586e2fddd07e55900a381255d6b3d9242ae87"
integrity sha512-kPF1Nmr5xMLz6DT7qEttz0TTeyx1x6SozIkNO9y4F2yxuWjHMp/e70fo742pR3y0MewgXQQMIIXeSKLB66iO7Q==
eslint-plugin-jsx-a11y@6.2.3:
version "6.2.3"
@ -7768,13 +7679,13 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
file-loader@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.0.0.tgz#c3570783fefb6e1bc0978a856f4bf5825b966c2a"
integrity sha512-roAbL6IdSGczwfXxhMi6Zq+jD4IfUpL0jWHD7fvmjdOVb7xBfdRUHe4LpBgO23VtVK5AW1OlWZo0p34Jvx3iWg==
file-loader@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.1.0.tgz#3a763391bc9502da7c59612fe348e38fc1980336"
integrity sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw==
dependencies:
loader-utils "^1.2.2"
schema-utils "^1.0.0"
loader-utils "^1.2.3"
schema-utils "^2.0.0"
file-loader@^3.0.1:
version "3.0.1"
@ -7967,10 +7878,10 @@ flatted@^2.0.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
flow-bin@0.102.0:
version "0.102.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.102.0.tgz#3d5de44bcc26d26585e932b3201988b766f9b380"
integrity sha512-mYon6noeLO0Q5SbiWULLQeM1L96iuXnRtYMd47j3bEWXAwUW9EnwNWcn+cZg/jC/Dg4Wj/jnkdTDEuFtbeu1ww==
flow-bin@0.103.0:
version "0.103.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.103.0.tgz#7aec510d85e1c1b0f2b912bb988337d70035cb0f"
integrity sha512-Y3yrnE5ICN1Kl/y10BwjA3JSuS+gt4jVPNyUNCZb0RqmkdssMrW8QNNysJYvhgAY/JBJH8Qv7NVUf11MiwfSlA==
flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
version "1.1.1"
@ -10636,6 +10547,11 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
jsqr@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.2.0.tgz#f93fc65fa7d1ded78b1bcb020fa044352b04261a"
integrity sha512-wKcQS9QC2VHGk7aphWCp1RrFyC0CM6fMgC5prZZ2KV/Lk6OKNoCod9IR6bao+yx3KPY0gZFC5dc+h+KFzCI0Wg==
jss-plugin-camel-case@10.0.0-alpha.17:
version "10.0.0-alpha.17"
resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0-alpha.17.tgz#6f7c9d9742e349bb061e53cd9b1c3cb006169a67"
@ -11039,7 +10955,7 @@ loader-runner@^2.3.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.2, loader-utils@^1.2.3:
loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@ -13110,7 +13026,7 @@ postcss-minify-selectors@^4.0.2:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
postcss-mixins@^6.2.0:
postcss-mixins@6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-6.2.2.tgz#3acea63271e2c75db62fb80bc1c29e1a609a4742"
integrity sha512-QqEZamiAMguYR6d2h73XXEHZgkxs03PlbU0PqgqtdCnbRlMLFNQgsfL/Td0rjIe2SwpLXOQyB9uoiLWa4GR7tg==
@ -13965,10 +13881,10 @@ react-helmet-async@^1.0.2:
react-fast-compare "2.0.4"
shallowequal "1.1.0"
react-hot-loader@4.12.7:
version "4.12.7"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.7.tgz#51ed57ee46c5d4d3906e58e8cdcd3f6ceeb1c0ec"
integrity sha512-ejnGcNttqIsgaLEpCl3KHLzFfKiEKHz/VTLYv57/xKQoryDMXQ/w31+jicrOAiCStYsY+KvrulVqkOqqkRaifg==
react-hot-loader@4.12.8:
version "4.12.8"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.8.tgz#90ecf2ef7d4005e110292760f135c95177e804c7"
integrity sha512-/Df2J3znMHzRzI6CW0dTOIWD2sjkVHxv56XCqujAo9mR+k2PVTiGjUgYBiGPGsix9zQzgCRfOKca93o9Zdj2vQ==
dependencies:
fast-levenshtein "^2.0.6"
global "^4.3.0"
@ -14053,6 +13969,15 @@ react-popper@^1.3.3:
typed-styles "^0.0.7"
warning "^4.0.2"
react-qr-reader@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-qr-reader/-/react-qr-reader-2.2.1.tgz#dc89046d1c1a1da837a683dd970de5926817d55b"
integrity sha512-EL5JEj53u2yAOgtpAKAVBzD/SiKWn0Bl7AZy6ZrSf1lub7xHwtaXe6XSx36Wbhl1VMGmvmrwYMRwO1aSCT2fwA==
dependencies:
jsqr "^1.2.0"
prop-types "^15.7.2"
webrtc-adapter "^7.2.1"
react-redux@7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.0.tgz#72af7cf490a74acdc516ea9c1dd80e25af9ea0b2"
@ -14917,6 +14842,13 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
rtcpeerconnection-shim@^1.2.15:
version "1.2.15"
resolved "https://registry.yarnpkg.com/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz#e7cc189a81b435324c4949aa3dfb51888684b243"
integrity sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==
dependencies:
sdp "^2.6.0"
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@ -15052,6 +14984,14 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
schema-utils@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.0.1.tgz#1eec2e059556af841b7f3a83b61af13d7a3f9196"
integrity sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
scrypt-js@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4"
@ -15101,6 +15041,11 @@ scryptsy@^1.2.1:
dependencies:
pbkdf2 "^3.0.3"
sdp@^2.6.0, sdp@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/sdp/-/sdp-2.9.0.tgz#2eed2d9c0b26c81ff87593107895c68d6fb9a0a6"
integrity sha512-XAVZQO4qsfzVTHorF49zCpkdxiGmPNjA8ps8RcJGtGP3QJ/A8I9/SVg/QnkAFDMXIyGbHZBBFwYBw6WdnhT96w==
seamless-immutable@^7.1.3:
version "7.1.4"
resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8"
@ -16566,7 +16511,7 @@ truffle-compile@^4.1.5:
truffle-error "^0.0.5"
truffle-expect "^0.0.9"
truffle-config@^1.0.1, truffle-config@^1.1.14, truffle-config@^1.1.15:
truffle-config@^1.0.1, truffle-config@^1.1.15:
version "1.1.15"
resolved "https://registry.yarnpkg.com/truffle-config/-/truffle-config-1.1.15.tgz#9c4e647f32d7fe78017f6823a81d3048376bc460"
integrity sha512-boLm/g36ytCtwNLaFZ7ZRv7Y1Ar0lCqgUxi2NkWwFeVoIv7V7ew27v8mziztmVvOXSSXowtkZfWNm4HAYVCWIw==
@ -16627,10 +16572,10 @@ truffle-contract@4.0.0-next.0:
web3-eth-abi "1.0.0-beta.35"
web3-utils "1.0.0-beta.35"
truffle-contract@4.0.24:
version "4.0.24"
resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-4.0.24.tgz#3e50774bb88f7552f2c01bde80fcdae4ccf9fd42"
integrity sha512-Zcv6rr2iMJWtQo+GbSHTtih13rLh3VU1SLMiT7odDd0fNiin2lBlLnJlIU2Xpfh06CP9240LAU2r6H0g81uCAA==
truffle-contract@4.0.25, truffle-contract@^4.0.25:
version "4.0.25"
resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-4.0.25.tgz#dc58c3ad20a4b1654efc0d55020e3bb8b9adac66"
integrity sha512-ngy+ljTCSs/Arv4/9xUjq6mBdAHvQjFvJGqMcBDoNZSXUdvuW4qmX9gu/cOFEt2hdsBcpt0sx7DrXGlY4O97ww==
dependencies:
bignumber.js "^7.2.1"
ethers "^4.0.0-beta.1"
@ -16653,23 +16598,7 @@ truffle-contract@^2.0.3:
truffle-contract-schema "^0.0.5"
web3 "^0.20.1"
truffle-contract@^4.0.25:
version "4.0.25"
resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-4.0.25.tgz#dc58c3ad20a4b1654efc0d55020e3bb8b9adac66"
integrity sha512-ngy+ljTCSs/Arv4/9xUjq6mBdAHvQjFvJGqMcBDoNZSXUdvuW4qmX9gu/cOFEt2hdsBcpt0sx7DrXGlY4O97ww==
dependencies:
bignumber.js "^7.2.1"
ethers "^4.0.0-beta.1"
truffle-blockchain-utils "^0.0.10"
truffle-contract-schema "^3.0.11"
truffle-error "^0.0.5"
truffle-interface-adapter "^0.2.0"
web3 "1.0.0-beta.37"
web3-core-promievent "1.0.0-beta.37"
web3-eth-abi "1.0.0-beta.37"
web3-utils "1.0.0-beta.37"
truffle-core@^5.0.27:
truffle-core@^5.0.28:
version "5.0.28"
resolved "https://registry.yarnpkg.com/truffle-core/-/truffle-core-5.0.28.tgz#0826dac6a5f8c50b498e8e6183bb5f46e8ad4ba8"
integrity sha512-gpoXHvWcDElaH8d8JjCMogJZMtNutpb/7dAhcadZ5Ik4HnPLOVbjFFkMTaH56x3jL4p52NjcktQV3ZmQZ6/rew==
@ -16911,17 +16840,17 @@ truffle-resolver@^5.0.14:
truffle-expect "^0.0.9"
truffle-provisioner "^0.1.5"
truffle-solidity-loader@0.1.26:
version "0.1.26"
resolved "https://registry.yarnpkg.com/truffle-solidity-loader/-/truffle-solidity-loader-0.1.26.tgz#b7b517d0d375e303211c5030642e19779715b1ee"
integrity sha512-qa5LGNxP+l5e4kYLz8PdliZZxf0zXzAPh/g56OdX7f/RW4nAM1yYF+1aUafWLWQuhtl85X+P+iA3BSWHYXJ+4A==
truffle-solidity-loader@0.1.27:
version "0.1.27"
resolved "https://registry.yarnpkg.com/truffle-solidity-loader/-/truffle-solidity-loader-0.1.27.tgz#e4c06e52d8cbb64612827652244a89ec338fdbf0"
integrity sha512-t4DTulVIan1J7BFxgwyeF1z7S4nLo0z4n/WwyPu+9WRvAruxcCoQG45wUTGdjLyPpuNV2iBjt5Hbv/UV0uElNw==
dependencies:
chalk "^1.1.3"
find-up "^1.1.2"
loader-utils "^1.1.0"
schema-utils "^1.0.0"
truffle-config "^1.1.14"
truffle-core "^5.0.27"
truffle-config "^1.1.15"
truffle-core "^5.0.28"
truffle-solidity-utils@^1.2.3:
version "1.2.3"
@ -16942,10 +16871,10 @@ truffle-workflow-compile@^2.0.24:
truffle-external-compile "^1.0.11"
truffle-resolver "^5.0.14"
truffle@5.0.27:
version "5.0.27"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.27.tgz#2842539f69d55a7cf82d21c4ffab0605953e1942"
integrity sha512-MN02w3TNOwVaEvvCD535pov7D/59p92yzQ/Os4hR2kiSGy1s+9V6RYtmOPucY8G/1wx8RnvOAgcIzcqHrO6tvQ==
truffle@5.0.28:
version "5.0.28"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.28.tgz#383c6c4ae082c52fe6dda6620f924ae3a0f5ac49"
integrity sha512-ccpvgrHW7EGfJ4cowMgh5dCAILG5Ie8q2QNBhnGtHxyPTDxb8o2/sgVIi5BZhaueijOPb47sYe2ojKlY6dk8+Q==
dependencies:
app-module-path "^2.2.0"
mocha "5.2.0"
@ -18546,36 +18475,7 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0:
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack@4.35.3:
version "4.35.3"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.35.3.tgz#66bc35ef215a7b75e8790f84d560013ffecf0ca3"
integrity sha512-xggQPwr9ILlXzz61lHzjvgoqGU08v5+Wnut19Uv3GaTtzN4xBTcwnobodrXE142EL1tOiS5WVEButooGzcQzTA==
dependencies:
"@webassemblyjs/ast" "1.8.5"
"@webassemblyjs/helper-module-context" "1.8.5"
"@webassemblyjs/wasm-edit" "1.8.5"
"@webassemblyjs/wasm-parser" "1.8.5"
acorn "^6.2.0"
ajv "^6.1.0"
ajv-keywords "^3.1.0"
chrome-trace-event "^1.0.0"
enhanced-resolve "^4.1.0"
eslint-scope "^4.0.0"
json-parse-better-errors "^1.0.2"
loader-runner "^2.3.0"
loader-utils "^1.1.0"
memory-fs "~0.4.1"
micromatch "^3.1.8"
mkdirp "~0.5.0"
neo-async "^2.5.0"
node-libs-browser "^2.0.0"
schema-utils "^1.0.0"
tapable "^1.1.0"
terser-webpack-plugin "^1.1.0"
watchpack "^1.5.0"
webpack-sources "^1.3.0"
webpack@^4.33.0:
webpack@4.36.1, webpack@^4.33.0:
version "4.36.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.36.1.tgz#f546fda7a403a76faeaaa7196c50d12370ed18a9"
integrity sha512-Ej01/N9W8DVyhEpeQnbUdGvOECw0L46FxS12cCOs8gSK7bhUlrbHRnWkjiXckGlHjUrmL89kDpTRIkUk6Y+fKg==
@ -18604,6 +18504,14 @@ webpack@^4.33.0:
watchpack "^1.5.0"
webpack-sources "^1.3.0"
webrtc-adapter@^7.2.1:
version "7.2.8"
resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-7.2.8.tgz#1373fa874559c655aa713830c2836511588d77ab"
integrity sha512-d/rZVIIqqPqu/1I9rabhI+hmVhNtT+MoJk0eipCJasiVM9L9ZOTBoVhZmtC/naB4G8GTvnCaassrDz5IqWZP6w==
dependencies:
rtcpeerconnection-shim "^1.2.15"
sdp "^2.9.0"
websocket-driver@>=0.5.1:
version "0.7.3"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"