Merge pull request #161 from gnosis/134-settings-improvements

Feature #134: Settings improvements
This commit is contained in:
Mikhail Mikheev 2019-09-10 11:12:18 +04:00 committed by GitHub
commit 4ed4fa56e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 715 additions and 864 deletions

View File

@ -23,6 +23,8 @@
"import/no-extraneous-dependencies": 0,
"import/extensions": 0,
"import/prefer-default-export": 0,
"react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
// https://github.com/yannickcr/eslint-plugin-react/issues/1593 ^
"jsx-a11y/label-has-for": 0,
"indent": ["error", 2, { "SwitchCase": 1 }],
"no-console": ["error", { "allow": ["warn", "error"] }],

View File

@ -31,15 +31,15 @@
"dependencies": {
"@gnosis.pm/safe-contracts": "^1.0.0",
"@gnosis.pm/util-contracts": "2.0.1",
"@material-ui/core": "4.4.0",
"@material-ui/icons": "4.2.1",
"@material-ui/core": "4.4.1",
"@material-ui/icons": "4.4.1",
"@testing-library/jest-dom": "4.1.0",
"@welldone-software/why-did-you-render": "3.3.3",
"axios": "0.19.0",
"bignumber.js": "9.0.0",
"connected-react-router": "6.5.2",
"date-fns": "2.0.1",
"ethereum-ens": "^0.7.7",
"date-fns": "2.1.0",
"ethereum-ens": "0.7.8",
"final-form": "4.18.5",
"history": "^4.7.2",
"immortal-db": "^1.0.2",
@ -64,11 +64,11 @@
"web3": "1.2.1"
},
"devDependencies": {
"@babel/cli": "7.5.5",
"@babel/core": "7.5.5",
"@babel/cli": "7.6.0",
"@babel/core": "7.6.0",
"@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-decorators": "7.6.0",
"@babel/plugin-proposal-do-expressions": "7.6.0",
"@babel/plugin-proposal-export-default-from": "7.5.2",
"@babel/plugin-proposal-export-namespace-from": "7.5.2",
"@babel/plugin-proposal-function-bind": "^7.0.0",
@ -77,15 +77,15 @@
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "7.6.0",
"@babel/plugin-proposal-pipeline-operator": "7.5.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@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.5",
"@babel/polyfill": "7.6.0",
"@babel/preset-env": "7.6.0",
"@babel/preset-flow": "^7.0.0-beta.40",
"@babel/preset-react": "^7.0.0-beta.40",
"@sambego/storybook-state": "^1.0.7",
@ -93,7 +93,7 @@
"@storybook/addon-knobs": "5.1.11",
"@storybook/addon-links": "5.1.11",
"@storybook/react": "5.1.11",
"@testing-library/react": "9.1.3",
"@testing-library/react": "9.1.4",
"autoprefixer": "9.6.1",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.0.3",
@ -107,15 +107,15 @@
"detect-port": "^1.2.2",
"eslint": "5.16.0",
"eslint-config-airbnb": "18.0.1",
"eslint-plugin-flowtype": "4.2.0",
"eslint-plugin-flowtype": "4.3.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "22.16.0",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.14.3",
"ethereumjs-abi": "0.6.8",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "4.2.0",
"flow-bin": "0.106.3",
"flow-bin": "0.107.0",
"fs-extra": "8.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.0.4",
@ -132,14 +132,14 @@
"storybook-host": "5.1.0",
"storybook-router": "^0.3.3",
"style-loader": "1.0.0",
"truffle": "5.0.34",
"truffle": "5.0.35",
"truffle-contract": "4.0.31",
"truffle-solidity-loader": "0.1.32",
"uglifyjs-webpack-plugin": "2.2.0",
"url-loader": "^2.1.0",
"webpack": "4.39.3",
"webpack-bundle-analyzer": "3.4.1",
"webpack-cli": "3.3.7",
"webpack-cli": "3.3.8",
"webpack-dev-server": "3.8.0",
"webpack-manifest-plugin": "^2.0.0-rc.2"
}

View File

@ -20,11 +20,7 @@ export const SharedSnackbar = () => (
autoHideDuration={4000}
onClose={closeSnackbar}
>
<SnackbarContent
onClose={closeSnackbar}
message={message}
variant={variant}
/>
<SnackbarContent onClose={closeSnackbar} message={message} variant={variant} />
</Snackbar>
)
}}
@ -60,10 +56,14 @@ type State = {
}
export class SharedSnackbarProvider extends React.Component<Props, State> {
state = {
isOpen: false,
message: '',
variant: 'info',
constructor(props: Props) {
super(props)
this.state = {
isOpen: false,
message: '',
variant: 'info',
}
}
openSnackbar = (message: string, variant: Variant) => {

View File

@ -20,9 +20,10 @@ type Props<K> = {
classes: Object,
children: Function,
size: number,
defaultFixed?: boolean,
defaultOrder?: 'desc' | 'asc',
defaultFixed: boolean,
defaultOrder: 'desc' | 'asc',
noBorder: boolean,
disablePagination: boolean,
}
type State = {
@ -142,6 +143,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
classes,
children,
size,
disablePagination,
defaultOrderBy,
defaultOrder,
defaultFixed,
@ -160,10 +162,11 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
input: classes.white,
}
const sortedData = stableSort(data, getSorting(orderParam, orderByParam, orderProp), fixedParam).slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage,
)
let sortedData = stableSort(data, getSorting(orderParam, orderByParam, orderProp), fixedParam)
if (!disablePagination) {
sortedData = sortedData.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
}
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage)
const isEmpty = size === 0
@ -184,18 +187,20 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
<CircularProgress size={60} />
</Row>
)}
<TablePagination
component="div"
count={size}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[5, 10, 25, 50, 100]}
page={page}
backIconButtonProps={backProps}
nextIconButtonProps={nextProps}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
classes={paginationClasses}
/>
{!disablePagination && (
<TablePagination
component="div"
count={size}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[5, 10, 25, 50, 100]}
page={page}
backIconButtonProps={backProps}
nextIconButtonProps={nextProps}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
classes={paginationClasses}
/>
)}
</>
)
}
@ -203,6 +208,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
GnoTable.defaultProps = {
defaultOrder: 'asc',
disablePagination: false,
}
export default withStyles(styles)(GnoTable)

View File

@ -1,6 +1,8 @@
// @flow
import { type FieldValidator } from 'final-form'
import { List } from 'immutable'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { sameAddress } from '~/logic/wallets/ethAddresses'
export const simpleMemoize = (fn: Function) => {
let lastArg
@ -68,7 +70,10 @@ export const minMaxLength = (minLen: string | number, maxLen: string | number) =
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
export const uniqueAddress = (addresses: string[]) => simpleMemoize((value: string) => (addresses.includes(value) ? ADDRESS_REPEATED_ERROR : undefined))
export const uniqueAddress = (addresses: string[] | List<string>) => simpleMemoize((value: string) => {
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
})
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => validators.reduce((error, validator) => error || validator(value), undefined)
@ -82,7 +87,7 @@ export const inLimit = (limit: number, base: number, baseText: string, symbol: s
return `Should not exceed ${max} ${symbol} (amount to reach ${baseText})`
}
export const differentFrom = (diffValue: string) => (value: string) => {
export const differentFrom = (diffValue: string | number) => (value: string) => {
if (value === diffValue.toString()) {
return `Value should be different than ${value}`
}

View File

@ -12,6 +12,7 @@ type Props = {
weight?: 'light' | 'regular' | 'bolder' | 'bold',
color?: 'soft' | 'medium' | 'dark' | 'white' | 'fancy' | 'primary' | 'secondary' | 'warning' | 'disabled' | 'error',
testId?: string,
className?: string,
}
const GnoButtonLink = ({
@ -20,7 +21,8 @@ const GnoButtonLink = ({
weight = 'regular',
color = 'secondary',
testId = '',
className = '',
...props
}: Props) => <button type={type} className={cx(styles.btnLink, size, color, weight)} data-testid={testId} {...props} />
}: Props) => <button type={type} className={cx(styles.btnLink, size, color, weight, className)} data-testid={testId} {...props} />
export default GnoButtonLink

View File

@ -15,19 +15,20 @@ type Props = {
tag: HeadingTag,
truncate?: boolean,
children: React.Node,
className?: string,
testId?: string,
}
class Heading extends React.PureComponent<Props> {
render() {
const {
align, tag, truncate, margin, color, children, testId, ...props
} = this.props
const Heading = (props: Props) => {
const {
align, tag, truncate, margin, color, children, testId, className = '', ...rest
} = props
const className = cx('heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, { truncate })
const classes = cx(className, 'heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, {
truncate,
})
return React.createElement(tag, { ...props, className, 'data-testid': testId || '' }, children)
}
return React.createElement(tag, { ...rest, className: classes, 'data-testid': testId || '' }, children)
}
export default Heading

View File

@ -4,3 +4,4 @@ export * from './send'
export * from './safeBlockchainOperations'
export * from './safeTxSignerEIP712'
export * from './txHistory'
export * from './notifications'

View File

@ -0,0 +1,14 @@
// @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',
}

View File

@ -108,11 +108,9 @@ export const executeTransaction = async (
.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: ${error}`)
console.log(`Error executing the TX: ${errMsg}`)
/* eslint-enable */
return 0
throw error;
}
}

View File

@ -78,7 +78,7 @@ const ReviewTx = ({
}
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.headingText} noMargin>
Send Funds
@ -154,7 +154,7 @@ const ReviewTx = ({
SUBMIT
</Button>
</Row>
</React.Fragment>
</>
)
}}
</SharedSnackbarConsumer>

View File

@ -131,10 +131,12 @@ class Balances extends React.Component<Props, State> {
color="secondary"
disableRipple
/>
<Paragraph className={classes.zero}>Hide zero balances</Paragraph>
<Paragraph size="lg">Hide zero balances</Paragraph>
</Col>
<Col xs={6} end="sm">
<ButtonLink onClick={this.onShow('Token')} testId="manage-tokens-btn">Manage Tokens</ButtonLink>
<ButtonLink size="lg" onClick={this.onShow('Token')} testId="manage-tokens-btn">
Manage Tokens
</ButtonLink>
<Modal
title="Manage Tokens"
description="Enable and disable tokens to be listed"

View File

@ -6,9 +6,6 @@ export const styles = (theme: Object) => ({
width: '20px',
marginRight: sm,
},
zero: {
letterSpacing: '-0.5px',
},
message: {
margin: `${sm} 0`,
},

View File

@ -18,8 +18,9 @@ import {
sm, xs, secondary, smallFontSize, border, secondaryText,
} from '~/theme/variables'
import { copyToClipboard } from '~/utils/clipboard'
import { type Actions } from '../container/actions'
import Balances from './Balances'
import Transactions from './TransactionsNew'
import Transactions from './Transactions'
import Settings from './Settings'
export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn'
@ -31,14 +32,11 @@ type State = {
tabIndex: number,
}
type Props = SelectorProps & {
classes: Object,
granted: boolean,
updateSafe: Function,
createTransaction: Function,
processTransaction: Function,
fetchTransactions: Function,
}
type Props = SelectorProps &
Actions & {
classes: Object,
granted: boolean,
}
const openIconStyle = {
height: '16px',
@ -152,7 +150,7 @@ class Layout extends React.Component<Props, State> {
<Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TEST_ID} />
</Tabs>
</Row>
<Hairline color={border} />
<Hairline color={border} style={{ marginTop: '-2px' }} />
{tabIndex === 0 && (
<Balances
ethBalance={ethBalance}

View File

@ -5,21 +5,15 @@ import Block from '~/components/layout/Block'
import Col from '~/components/layout/Col'
import Field from '~/components/forms/Field'
import Heading from '~/components/layout/Heading'
import { SharedSnackbarConsumer } from '~/components/SharedSnackBar'
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
import TextField from '~/components/forms/TextField'
import GnoForm from '~/components/forms/GnoForm'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph'
import Hairline from '~/components/layout/Hairline'
import Button from '~/components/layout/Button'
import { sm } from '~/theme/variables'
import { styles } from './style'
const controlsStyle = {
backgroundColor: 'white',
padding: sm,
}
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
@ -28,24 +22,26 @@ type Props = {
safeAddress: string,
safeName: string,
updateSafe: Function,
openSnackbar: Function,
}
const ChangeSafeName = (props: Props) => {
const {
classes, safeAddress, safeName, updateSafe,
classes, safeAddress, safeName, updateSafe, openSnackbar,
} = props
const handleSubmit = (values) => {
updateSafe({ address: safeAddress, name: values.safeName })
openSnackbar('Safe name changed', 'success')
}
return (
<React.Fragment>
<>
<GnoForm onSubmit={handleSubmit}>
{() => (
<React.Fragment>
<>
<Block className={classes.formContainer}>
<Heading tag="h3">Modify Safe name</Heading>
<Heading tag="h2">Modify Safe name</Heading>
<Paragraph>
You can change the name of this Safe. This name is only stored locally and never shared with Gnosis or
any third parties.
@ -63,8 +59,7 @@ const ChangeSafeName = (props: Props) => {
/>
</Block>
</Block>
<Hairline />
<Row style={controlsStyle} align="end" grow>
<Row className={classes.controlsRow} align="end" grow>
<Col end="xs">
<Button
type="submit"
@ -74,15 +69,21 @@ const ChangeSafeName = (props: Props) => {
color="primary"
testId={SAFE_NAME_SUBMIT_BTN_TEST_ID}
>
SAVE
Save
</Button>
</Col>
</Row>
</React.Fragment>
</>
)}
</GnoForm>
</React.Fragment>
</>
)
}
export default withStyles(styles)(ChangeSafeName)
const withSnackbar = (props) => (
<SharedSnackbarConsumer>
{({ openSnackbar }) => <ChangeSafeName {...props} openSnackbar={openSnackbar} />}
</SharedSnackbarConsumer>
)
export default withStyles(styles)(withSnackbar)

View File

@ -1,10 +1,11 @@
// @flow
import { lg, sm, boldFont } from '~/theme/variables'
import {
lg, sm, boldFont, border,
} from '~/theme/variables'
export const styles = () => ({
formContainer: {
padding: lg,
minHeight: '369px',
},
root: {
display: 'flex',
@ -14,4 +15,12 @@ export const styles = () => ({
marginRight: sm,
fontWeight: boldFont,
},
controlsRow: {
padding: lg,
position: 'absolute',
bottom: 0,
boxSizing: 'border-box',
width: '100%',
borderTop: `2px solid ${border}`,
},
})

View File

@ -82,7 +82,7 @@ const AddOwner = ({
}
const ownerSubmitted = (newValues: Object) => {
setValues(stateValues => ({
setValues((stateValues) => ({
...stateValues,
ownerName: newValues.ownerName,
ownerAddress: newValues.ownerAddress,
@ -91,7 +91,7 @@ const AddOwner = ({
}
const thresholdSubmitted = (newValues: Object) => {
setValues(stateValues => ({
setValues((stateValues) => ({
...stateValues,
threshold: newValues.threshold,
}))
@ -99,7 +99,7 @@ const AddOwner = ({
}
return (
<React.Fragment>
<>
<SharedSnackbarConsumer>
{({ openSnackbar }) => {
const onAddOwner = async () => {
@ -120,7 +120,7 @@ const AddOwner = ({
open={isOpen}
paperClassName={classes.biggerModalWindow}
>
<React.Fragment>
<>
{activeScreen === 'selectOwner' && (
<OwnerForm onClose={onClose} onSubmit={ownerSubmitted} owners={owners} />
)}
@ -144,12 +144,12 @@ const AddOwner = ({
onSubmit={onAddOwner}
/>
)}
</React.Fragment>
</>
</Modal>
)
}}
</SharedSnackbarConsumer>
</React.Fragment>
</>
)
}

View File

@ -16,10 +16,7 @@ import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import { type Owner } from '~/routes/safe/store/models/owner'
import {
composeValidators,
required,
minMaxLength,
uniqueAddress,
composeValidators, required, minMaxLength, uniqueAddress,
} from '~/components/forms/validator'
import { styles } from './style'
@ -46,10 +43,10 @@ const OwnerForm = ({
const handleSubmit = (values) => {
onSubmit(values)
}
const ownerDoesntExist = uniqueAddress(owners.map(o => o.address))
const ownerDoesntExist = uniqueAddress(owners.map((o) => o.address))
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.manage} noMargin>
Add new owner
@ -65,7 +62,7 @@ const OwnerForm = ({
const mutators = args[3]
return (
<React.Fragment>
<>
<Block className={classes.formContainer}>
<Row margin="md">
<Paragraph>Add a new owner to the active Safe</Paragraph>
@ -114,11 +111,11 @@ const OwnerForm = ({
Next
</Button>
</Row>
</React.Fragment>
</>
)
}}
</GnoForm>
</React.Fragment>
</>
)
}

View File

@ -11,10 +11,10 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',
lineHeight: 'normal',
},
manage: {
fontSize: '24px',

View File

@ -14,10 +14,10 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',
lineHeight: 'normal',
},
manage: {
fontSize: '24px',

View File

@ -11,10 +11,10 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',
lineHeight: 'normal',
},
manage: {
fontSize: '24px',

View File

@ -5,9 +5,8 @@ import {
export const styles = () => ({
heading: {
padding: `${sm} ${lg}`,
padding: lg,
justifyContent: 'space-between',
maxHeight: '75px',
boxSizing: 'border-box',
},
manage: {
@ -15,7 +14,6 @@ export const styles = () => ({
},
container: {
padding: `${md} ${lg}`,
paddingBottom: '40px',
},
close: {
height: '35px',

View File

@ -46,7 +46,9 @@ export const sendRemoveOwner = async (
) => {
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const safeOwners = await gnosisSafe.getOwners()
const index = safeOwners.findIndex(ownerAddress => ownerAddress.toLowerCase() === ownerAddressToRemove.toLowerCase())
const index = safeOwners.findIndex(
(ownerAddress) => ownerAddress.toLowerCase() === ownerAddressToRemove.toLowerCase(),
)
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
const txData = gnosisSafe.contract.methods
.removeOwner(prevAddress, ownerAddressToRemove, values.threshold)
@ -103,26 +105,21 @@ const RemoveOwner = ({
}
return (
<React.Fragment>
<>
<SharedSnackbarConsumer>
{({ openSnackbar }) => {
const onRemoveOwner = () => {
onClose()
try {
sendRemoveOwner(
values,
safeAddress,
ownerAddress,
ownerName,
owners,
openSnackbar,
createTransaction,
removeSafeOwner,
)
} catch (error) {
// eslint-disable-next-line
console.log('Error while removing an owner ' + error)
}
sendRemoveOwner(
values,
safeAddress,
ownerAddress,
ownerName,
owners,
openSnackbar,
createTransaction,
removeSafeOwner,
)
}
return (
@ -133,7 +130,7 @@ const RemoveOwner = ({
open={isOpen}
paperClassName={classes.biggerModalWindow}
>
<React.Fragment>
<>
{activeScreen === 'checkOwner' && (
<CheckOwner
onClose={onClose}
@ -165,12 +162,12 @@ const RemoveOwner = ({
onSubmit={onRemoveOwner}
/>
)}
</React.Fragment>
</>
</Modal>
)
}}
</SharedSnackbarConsumer>
</React.Fragment>
</>
)
}

View File

@ -34,19 +34,14 @@ type Props = {
}
const CheckOwner = ({
classes,
onClose,
ownerAddress,
ownerName,
network,
onSubmit,
classes, onClose, ownerAddress, ownerName, network, onSubmit,
}: Props) => {
const handleSubmit = (values) => {
onSubmit(values)
}
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.manage} noMargin>
Remove owner
@ -59,9 +54,7 @@ const CheckOwner = ({
<Hairline />
<Block className={classes.formContainer}>
<Row margin="md">
<Paragraph>
Review the owner you want to remove from the active Safe:
</Paragraph>
<Paragraph>Review the owner you want to remove from the active Safe:</Paragraph>
</Row>
<Row className={classes.owner}>
<Col xs={1} align="center">
@ -100,7 +93,7 @@ const CheckOwner = ({
Next
</Button>
</Row>
</React.Fragment>
</>
)
}

View File

@ -11,7 +11,7 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
lineHeight: 'normal',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',

View File

@ -56,7 +56,7 @@ const ReviewRemoveOwner = ({
}
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.manage} noMargin>
Remove owner
@ -91,11 +91,10 @@ const ReviewRemoveOwner = ({
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{values.threshold}
{' '}
out of
{' '}
out of
{owners.size - 1}
{' '}
owner(s)
owner(s)
</Paragraph>
</Block>
</Block>
@ -105,12 +104,12 @@ const ReviewRemoveOwner = ({
<Paragraph size="lg" color="primary" noMargin>
{owners.size - 1}
{' '}
Safe owner(s)
Safe owner(s)
</Paragraph>
</Row>
<Hairline />
{owners.map(
owner => owner.address !== ownerAddress && (
(owner) => owner.address !== ownerAddress && (
<React.Fragment key={owner.address}>
<Row className={classes.owner}>
<Col xs={1} align="center">
@ -159,7 +158,11 @@ const ReviewRemoveOwner = ({
<Paragraph size="md" color="disabled" noMargin>
{ownerAddress}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress, network)} target="_blank">
<Link
className={classes.open}
to={getEtherScanLink('address', ownerAddress, network)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
</Block>
@ -187,7 +190,7 @@ const ReviewRemoveOwner = ({
Submit
</Button>
</Row>
</React.Fragment>
</>
)
}

View File

@ -14,7 +14,7 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
lineHeight: 'normal',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',

View File

@ -40,7 +40,7 @@ const ThresholdForm = ({
const defaultThreshold = threshold > 1 ? threshold - 1 : threshold
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.manage} noMargin>
Remove owner
@ -56,7 +56,7 @@ const ThresholdForm = ({
const numOptions = owners.size > 1 ? owners.size - 1 : 1
return (
<React.Fragment>
<>
<Block className={classes.formContainer}>
<Row>
<Paragraph weight="bolder" className={classes.headingText}>
@ -72,8 +72,8 @@ const ThresholdForm = ({
<Col xs={2}>
<Field
name="threshold"
render={props => (
<React.Fragment>
render={(props) => (
<>
<SelectField {...props} disableError>
{[...Array(Number(numOptions))].map((x, index) => (
<MenuItem key={index} value={`${index + 1}`}>
@ -86,7 +86,7 @@ const ThresholdForm = ({
{props.meta.error}
</Paragraph>
)}
</React.Fragment>
</>
)}
validate={composeValidators(required, mustBeInteger, minValue(1), maxValue(numOptions))}
data-testid="threshold-select-input"
@ -119,11 +119,11 @@ owner(s)
Review
</Button>
</Row>
</React.Fragment>
</>
)
}}
</GnoForm>
</React.Fragment>
</>
)
}

View File

@ -11,7 +11,7 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
lineHeight: 'normal',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',

View File

@ -44,7 +44,9 @@ export const sendReplaceOwner = async (
) => {
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const safeOwners = await gnosisSafe.getOwners()
const index = safeOwners.findIndex(ownerAddress => ownerAddress.toLowerCase() === ownerAddressToRemove.toLowerCase())
const index = safeOwners.findIndex(
(ownerAddress) => ownerAddress.toLowerCase() === ownerAddressToRemove.toLowerCase(),
)
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
const txData = gnosisSafe.contract.methods
.swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress)
@ -97,7 +99,7 @@ const ReplaceOwner = ({
}
return (
<React.Fragment>
<>
<SharedSnackbarConsumer>
{({ openSnackbar }) => {
const onReplaceOwner = () => {
@ -127,7 +129,7 @@ const ReplaceOwner = ({
open={isOpen}
paperClassName={classes.biggerModalWindow}
>
<React.Fragment>
<>
{activeScreen === 'checkOwner' && (
<OwnerForm
onClose={onClose}
@ -152,12 +154,12 @@ const ReplaceOwner = ({
threshold={threshold}
/>
)}
</React.Fragment>
</>
</Modal>
)
}}
</SharedSnackbarConsumer>
</React.Fragment>
</>
)
}

View File

@ -21,10 +21,7 @@ import Link from '~/components/layout/Link'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { type Owner } from '~/routes/safe/store/models/owner'
import {
composeValidators,
required,
minMaxLength,
uniqueAddress,
composeValidators, required, minMaxLength, uniqueAddress,
} from '~/components/forms/validator'
import { styles } from './style'
import { secondary } from '~/theme/variables'
@ -60,10 +57,10 @@ const OwnerForm = ({
const handleSubmit = (values) => {
onSubmit(values)
}
const ownerDoesntExist = uniqueAddress(owners.map(o => o.address))
const ownerDoesntExist = uniqueAddress(owners.map((o) => o.address))
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph weight="bolder" className={classes.manage} noMargin>
Replace owner
@ -79,7 +76,7 @@ const OwnerForm = ({
const mutators = args[3]
return (
<React.Fragment>
<>
<Block className={classes.formContainer}>
<Row>
<Paragraph>
@ -162,11 +159,11 @@ const OwnerForm = ({
Next
</Button>
</Row>
</React.Fragment>
</>
)
}}
</GnoForm>
</React.Fragment>
</>
)
}

View File

@ -11,10 +11,10 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',
lineHeight: 'normal',
},
manage: {
fontSize: '24px',

View File

@ -14,10 +14,10 @@ export const styles = () => ({
maxHeight: '75px',
},
annotation: {
letterSpacing: '-1px',
color: secondaryText,
marginRight: 'auto',
marginLeft: '20px',
lineHeight: 'normal',
},
manage: {
fontSize: '24px',

View File

@ -1,5 +1,6 @@
// @flow
import React from 'react'
import cn from 'classnames'
import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles'
import TableRow from '@material-ui/core/TableRow'
@ -20,13 +21,17 @@ import EditOwnerModal from './EditOwnerModal'
import OwnerAddressTableCell from './OwnerAddressTableCell'
import type { Owner } from '~/routes/safe/store/models/owner'
import {
getOwnerData, generateColumns, OWNERS_TABLE_NAME_ID, OWNERS_TABLE_ADDRESS_ID, type OwnerRow,
getOwnerData,
generateColumns,
OWNERS_TABLE_NAME_ID,
OWNERS_TABLE_ADDRESS_ID,
type OwnerRow,
} from './dataFetcher'
import { lg, sm, boldFont } from '~/theme/variables'
import { styles } from './style'
import ReplaceOwnerIcon from './assets/icons/replace-owner.svg'
import RenameOwnerIcon from './assets/icons/rename-owner.svg'
import RemoveOwnerIcon from '../assets/icons/bin.svg'
import Paragraph from '~/components/layout/Paragraph/index'
export const RENAME_OWNER_BTN_TEST_ID = 'rename-owner-btn'
export const REMOVE_OWNER_BTN_TEST_ID = 'remove-owner-btn'
@ -34,20 +39,6 @@ export const ADD_OWNER_BTN_TEST_ID = 'add-owner-btn'
export const REPLACE_OWNER_BTN_TEST_ID = 'replace-owner-btn'
export const OWNERS_ROW_TEST_ID = 'owners-row'
const controlsStyle = {
backgroundColor: 'white',
padding: sm,
}
const addOwnerButtonStyle = {
marginRight: sm,
fontWeight: boldFont,
}
const title = {
padding: lg,
}
type Props = {
classes: Object,
safeAddress: string,
@ -76,13 +67,17 @@ type State = {
type Action = 'AddOwner' | 'EditOwner' | 'ReplaceOwner' | 'RemoveOwner'
class ManageOwners extends React.Component<Props, State> {
state = {
selectedOwnerAddress: undefined,
selectedOwnerName: undefined,
showAddOwner: false,
showRemoveOwner: false,
showReplaceOwner: false,
showEditOwner: false,
constructor(props) {
super(props)
this.state = {
selectedOwnerAddress: undefined,
selectedOwnerName: undefined,
showAddOwner: false,
showRemoveOwner: false,
showReplaceOwner: false,
showEditOwner: false,
}
}
onShow = (action: Action, row?: Object) => () => {
@ -127,24 +122,36 @@ class ManageOwners extends React.Component<Props, State> {
} = this.state
const columns = generateColumns()
const autoColumns = columns.filter(c => !c.custom)
const autoColumns = columns.filter((c) => !c.custom)
const ownerData = getOwnerData(owners)
return (
<React.Fragment>
<>
<Block className={classes.formContainer}>
<Heading tag="h3" style={title}>Manage Safe Owners</Heading>
<Heading tag="h2" className={classes.title}>
Manage Safe Owners
</Heading>
<Paragraph className={classes.annotation}>
Add, remove and replace owners or rename existing owners. Owner names are only stored locally and never
shared with Gnosis or any third parties.
</Paragraph>
<Table
label="Owners"
defaultOrderBy={OWNERS_TABLE_NAME_ID}
columns={columns}
data={ownerData}
size={ownerData.size}
disablePagination
defaultFixed
noBorder
>
{(sortedData: Array<OwnerRow>) => sortedData.map((row: any, index: number) => (
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={OWNERS_ROW_TEST_ID}>
{(sortedData: List<OwnerRow>) => sortedData.map((row: any, index: number) => (
<TableRow
tabIndex={-1}
key={index}
className={cn(classes.hide, index >= 3 && index === sortedData.size - 1 && classes.noBorderBottom)}
data-testid={OWNERS_ROW_TEST_ID}
>
{autoColumns.map((column: Column) => (
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td">
{column.id === OWNERS_TABLE_ADDRESS_ID ? (
@ -184,17 +191,15 @@ class ManageOwners extends React.Component<Props, State> {
)}
</TableCell>
</TableRow>
))
}
))}
</Table>
</Block>
{granted && (
<React.Fragment>
<>
<Hairline />
<Row style={controlsStyle} align="end" grow>
<Row className={classes.controlsRow} align="end" grow>
<Col end="xs">
<Button
style={addOwnerButtonStyle}
size="small"
variant="contained"
color="primary"
@ -205,7 +210,7 @@ class ManageOwners extends React.Component<Props, State> {
</Button>
</Col>
</Row>
</React.Fragment>
</>
)}
<AddOwnerModal
onClose={this.onHide('AddOwner')}
@ -257,7 +262,7 @@ class ManageOwners extends React.Component<Props, State> {
network={network}
editSafeOwner={editSafeOwner}
/>
</React.Fragment>
</>
)
}
}

View File

@ -3,7 +3,14 @@ import { lg } from '~/theme/variables'
export const styles = () => ({
formContainer: {
minHeight: '369px',
minHeight: '420px',
},
title: {
padding: lg,
paddingBottom: 0,
},
annotation: {
paddingLeft: lg,
},
hide: {
'&:hover': {
@ -16,6 +23,12 @@ export const styles = () => ({
actions: {
justifyContent: 'flex-end',
visibility: 'hidden',
minWidth: '100px',
},
noBorderBottom: {
'& > td': {
borderBottom: 'none',
},
},
editOwnerIcon: {
cursor: 'pointer',
@ -24,6 +37,11 @@ export const styles = () => ({
marginLeft: lg,
cursor: 'pointer',
},
controlsRow: {
backgroundColor: 'white',
padding: lg,
borderRadius: '8px',
},
removeOwnerIcon: {
marginLeft: lg,
cursor: 'pointer',

View File

@ -73,8 +73,10 @@ const RemoveSafeComponent = ({
<Row className={classes.description}>
<Paragraph noMargin>
Removing a Safe only removes it from your interface.
{' '}
<b>It does not delete the Safe</b>
. You can always add it back using the Safe&apos;s address.
. You can always add it
back using the Safe&apos;s address.
</Paragraph>
</Row>
</Block>

View File

@ -41,7 +41,7 @@ const ChangeThreshold = ({
}
return (
<React.Fragment>
<>
<Row align="center" grow className={classes.heading}>
<Paragraph className={classes.headingText} weight="bolder" noMargin>
Change required confirmations
@ -53,7 +53,7 @@ const ChangeThreshold = ({
<Hairline />
<GnoForm onSubmit={handleSubmit} initialValues={{ threshold: threshold.toString() }}>
{() => (
<React.Fragment>
<>
<Block className={classes.modalContent}>
<Row>
<Paragraph>
@ -63,14 +63,14 @@ const ChangeThreshold = ({
</Row>
<Row>
<Paragraph weight="bolder">
Any transaction over any daily limit requires the confirmation of:
Any transaction requires the confirmation of:
</Paragraph>
</Row>
<Row margin="xl" align="center" className={classes.inputRow}>
<Col xs={2}>
<Field
name={THRESHOLD_FIELD_NAME}
render={props => (
render={(props) => (
<>
<SelectField {...props} disableError>
{[...Array(Number(owners.size))].map((x, index) => (
@ -96,7 +96,7 @@ const ChangeThreshold = ({
{' '}
{owners.size}
{' '}
owner(s)
owner(s)
</Paragraph>
</Col>
</Row>
@ -110,10 +110,10 @@ const ChangeThreshold = ({
CHANGE
</Button>
</Row>
</React.Fragment>
</>
)}
</GnoForm>
</React.Fragment>
</>
)
}

View File

@ -21,19 +21,20 @@ type Props = {
classes: Object,
createTransaction: Function,
safeAddress: string,
granted: boolean,
}
const ThresholdSettings = ({
owners, threshold, classes, createTransaction, safeAddress,
owners, threshold, classes, createTransaction, safeAddress, granted,
}: Props) => {
const [isModalOpen, setModalOpen] = useState(false)
const toggleModal = () => {
setModalOpen(prevOpen => !prevOpen)
setModalOpen((prevOpen) => !prevOpen)
}
return (
<React.Fragment>
<>
<SharedSnackbarConsumer>
{({ openSnackbar }) => {
const onChangeThreshold = async (newThreshold) => {
@ -46,14 +47,11 @@ const ThresholdSettings = ({
return (
<>
<Block className={classes.container}>
<Heading tag="h3">Required confirmations</Heading>
<Heading tag="h2">Required confirmations</Heading>
<Paragraph>
Any transaction over any daily limit
<br />
{' '}
requires the confirmation of:
Any transaction requires the confirmation of:
</Paragraph>
<Paragraph size="xxl" className={classes.ownersText}>
<Paragraph size="lg" className={classes.ownersText}>
<Bold>{threshold}</Bold>
{' '}
out of
@ -62,8 +60,8 @@ const ThresholdSettings = ({
{' '}
owners
</Paragraph>
{owners.size > 1 && (
<Row align="center" className={classes.buttonRow}>
{owners.size > 1 && granted && (
<Row className={classes.buttonRow}>
<Button
color="primary"
minWidth={120}
@ -93,7 +91,7 @@ const ThresholdSettings = ({
)
}}
</SharedSnackbarConsumer>
</React.Fragment>
</>
)
}

View File

@ -1,32 +1,27 @@
// @flow
import {
fontColor, lg, smallFontSize, md, border, secondaryText,
fontColor, lg, smallFontSize, border, secondaryText,
} from '~/theme/variables'
export const styles = () => ({
ownersText: {
fontSize: '26px',
color: secondaryText,
'& b': {
color: fontColor,
},
},
container: {
height: '100%',
position: 'relative',
padding: lg,
},
buttonRow: {
padding: lg,
position: 'absolute',
bottom: '51px',
left: 0,
height: '51px',
width: '100%',
paddingRight: md,
display: 'flex',
justifyContent: 'flex-end',
borderTop: `solid 1px ${border}`,
bottom: 0,
boxSizing: 'border-box',
width: '100%',
justifyContent: 'flex-end',
borderTop: `2px solid ${border}`,
},
modifyBtn: {
height: '32px',

View File

@ -9,8 +9,8 @@ import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Span from '~/components/layout/Span'
import Img from '~/components/layout/Img'
import ButtonLink from '~/components/layout/ButtonLink'
import RemoveSafeModal from './RemoveSafeModal'
import Paragraph from '~/components/layout/Paragraph'
import Hairline from '~/components/layout/Hairline'
import { type Owner } from '~/routes/safe/store/models/owner'
import ChangeSafeName from './ChangeSafeName'
@ -39,6 +39,7 @@ type Props = Actions & {
createTransaction: Function,
addSafeOwner: Function,
removeSafeOwner: Function,
updateSafe: Function,
replaceSafeOwner: Function,
editSafeOwner: Function,
userAddress: string,
@ -47,12 +48,16 @@ type Props = Actions & {
type Action = 'RemoveSafe'
class Settings extends React.Component<Props, State> {
state = {
showRemoveSafe: false,
menuOptionIndex: 1,
constructor(props) {
super(props)
this.state = {
showRemoveSafe: false,
menuOptionIndex: 1,
}
}
handleChange = menuOptionIndex => () => {
handleChange = (menuOptionIndex) => () => {
this.setState({ menuOptionIndex })
}
@ -85,28 +90,19 @@ class Settings extends React.Component<Props, State> {
} = this.props
return (
<React.Fragment>
<Row align="center" className={classes.message}>
<Col xs={6}>
<Paragraph className={classes.settings} size="lg" weight="bolder">
Settings
</Paragraph>
</Col>
<Col xs={6} end="sm">
<Paragraph noMargin size="md" color="error" onClick={this.onShow('RemoveSafe')}>
<Span className={cn(classes.links, classes.removeSafeText)}>
Remove Safe
</Span>
<Img alt="Trash Icon" className={classes.removeSafeIcon} src={RemoveSafeIcon} />
</Paragraph>
<RemoveSafeModal
onClose={this.onHide('RemoveSafe')}
isOpen={showRemoveSafe}
etherScanLink={etherScanLink}
safeAddress={safeAddress}
safeName={safeName}
/>
</Col>
<>
<Row className={classes.message}>
<ButtonLink size="lg" color="error" className={classes.removeSafeBtn} onClick={this.onShow('RemoveSafe')}>
<Span className={classes.links}>Remove Safe</Span>
<Img alt="Trash Icon" className={classes.removeSafeIcon} src={RemoveSafeIcon} />
</ButtonLink>
<RemoveSafeModal
onClose={this.onHide('RemoveSafe')}
isOpen={showRemoveSafe}
etherScanLink={etherScanLink}
safeAddress={safeAddress}
safeName={safeName}
/>
</Row>
<Block className={classes.root}>
<Col xs={3} layout="column">
@ -128,17 +124,13 @@ class Settings extends React.Component<Props, State> {
)
</Row>
<Hairline />
{granted && (
<React.Fragment>
<Row
className={cn(classes.menuOption, menuOptionIndex === 3 && classes.active)}
onClick={this.handleChange(3)}
>
Required confirmations
</Row>
<Hairline />
</React.Fragment>
)}
<Row
className={cn(classes.menuOption, menuOptionIndex === 3 && classes.active)}
onClick={this.handleChange(3)}
>
Required confirmations
</Row>
<Hairline />
</Block>
</Col>
<Col xs={9} layout="column">
@ -162,18 +154,19 @@ class Settings extends React.Component<Props, State> {
granted={granted}
/>
)}
{granted && menuOptionIndex === 3 && (
{menuOptionIndex === 3 && (
<ThresholdSettings
owners={owners}
threshold={threshold}
createTransaction={createTransaction}
safeAddress={safeAddress}
granted={granted}
/>
)}
</Block>
</Col>
</Block>
</React.Fragment>
</>
)
}
}

View File

@ -1,26 +1,31 @@
// @flow
import {
sm, lg, border, secondary, bolderFont, background,
sm, md, lg, border, secondary, bolderFont, background, largeFontSize,
} from '~/theme/variables'
export const styles = () => ({
root: {
backgroundColor: 'white',
boxShadow: '0 -1px 4px 0 rgba(74, 85, 121, 0.5)',
minHeight: '400px',
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
minHeight: '505px',
display: 'flex',
borderRadius: '8px',
},
settings: {
letterSpacing: '-0.5px',
},
menu: {
borderRight: `solid 1px ${border}`,
borderRight: `solid 2px ${border}`,
height: '100%',
},
menuOption: {
padding: lg,
fontSize: largeFontSize,
padding: `${md} 0 ${md} ${lg}`,
alignItems: 'center',
cursor: 'pointer',
'&:first-child': {
borderRadius: '8px',
},
},
active: {
backgroundColor: background,
@ -29,9 +34,14 @@ export const styles = () => ({
},
container: {
height: '100%',
position: 'relative',
},
message: {
margin: `${sm} 0`,
padding: `${md} 0`,
maxHeight: '54px', // to make it the same as row in Balances component
boxSizing: 'border-box',
justifyContent: 'flex-end',
},
links: {
textDecoration: 'underline',
@ -39,13 +49,13 @@ export const styles = () => ({
cursor: 'pointer',
},
},
removeSafeText: {
height: '16px',
lineHeight: '16px',
paddingRight: sm,
float: 'left',
removeSafeBtn: {
display: 'flex',
alignItems: 'center',
marginTop: '-1px', // to make it the same as row in Balances component
},
removeSafeIcon: {
marginLeft: sm,
height: '16px',
cursor: 'pointer',
},

View File

@ -1,83 +0,0 @@
// @flow
import * as React from 'react'
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
import { withStyles } from '@material-ui/core/styles'
import Collapse from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton'
import ListItemText from '~/components/List/ListItemText'
import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import Avatar from '@material-ui/core/Avatar'
import Group from '@material-ui/icons/Group'
import Person from '@material-ui/icons/Person'
import ExpandLess from '@material-ui/icons/ExpandLess'
import ExpandMore from '@material-ui/icons/ExpandMore'
import { type WithStyles } from '~/theme/mui'
import { type Confirmation, type ConfirmationProps } from '~/routes/safe/store/models/confirmation'
const styles = {
nested: {
paddingLeft: '40px',
},
}
type Props = Open & WithStyles & {
confirmations: List<Confirmation>,
threshold: number,
}
const GnoConfirmation = ({ owner, type, hash }: ConfirmationProps) => {
const address = owner.get('address')
const confirmed = type === 'confirmation'
const text = confirmed ? 'Confirmed' : 'Not confirmed'
const hashText = confirmed ? `Confirmation hash: ${hash}` : undefined
return (
<React.Fragment>
<ListItem key={address}>
<ListItemIcon>
<Person />
</ListItemIcon>
<ListItemText
cut
primary={`${owner.get('name')} [${text}]`}
secondary={hashText}
/>
</ListItem>
</React.Fragment>
)
}
const Confirmaitons = openHoc(({
open, toggle, confirmations, threshold,
}: Props) => (
<React.Fragment>
<ListItem onClick={toggle}>
<Avatar>
<Group />
</Avatar>
<ListItemText primary="Threshold" secondary={`${threshold} confirmation${threshold === 1 ? '' : 's'} needed`} />
<ListItemIcon>
{open
? <IconButton disableRipple><ExpandLess /></IconButton>
: <IconButton disableRipple><ExpandMore /></IconButton>
}
</ListItemIcon>
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding style={{ width: '100%' }}>
{confirmations.map(confirmation => (
<GnoConfirmation
key={confirmation.get('owner').get('address')}
owner={confirmation.get('owner')}
type={confirmation.get('type')}
hash={confirmation.get('hash')}
/>
))}
</List>
</Collapse>
</React.Fragment>
))
export default withStyles(styles)(Confirmaitons)

View File

@ -1,52 +0,0 @@
// @flow
import * as React from 'react'
import { List as ImmutableList } from 'immutable'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem'
import ListItemText from '~/components/List/ListItemText'
import Avatar from '@material-ui/core/Avatar'
import Group from '@material-ui/icons/Group'
import MailOutline from '@material-ui/icons/MailOutline'
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
import Confirmations from './Confirmations'
type Props = {
safeName: string,
confirmations: ImmutableList<Confirmation>,
destination: string,
threshold: number,
}
const listStyle = {
width: '100%',
}
class Collapsed extends React.PureComponent<Props, {}> {
render() {
const {
confirmations, destination, safeName, threshold,
} = this.props
return (
<Row>
<Col sm={12} top="xs" overflow>
<List style={listStyle}>
<ListItem>
<Avatar><Group /></Avatar>
<ListItemText primary={safeName} secondary="Safe Name" />
</ListItem>
<Confirmations confirmations={confirmations} threshold={threshold} />
<ListItem>
<Avatar><MailOutline /></Avatar>
<ListItemText primary="Destination" secondary={destination} />
</ListItem>
</List>
</Col>
</Row>
)
}
}
export default Collapsed

View File

@ -5,8 +5,10 @@ import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph/index'
const NoRights = () => (
<Row>
export const NO_TRANSACTION_ROW_TEST_ID = 'no-transaction-row'
const NoTransactions = () => (
<Row data-testid={NO_TRANSACTION_ROW_TEST_ID}>
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
<Paragraph size="lg">
<Bold>No transactions found for this safe</Bold>
@ -15,4 +17,4 @@ const NoRights = () => (
</Row>
)
export default NoRights
export default NoTransactions

View File

@ -1,131 +0,0 @@
// @flow
import * as React from 'react'
import { List } from 'immutable'
import { connect } from 'react-redux'
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
import ExpandLess from '@material-ui/icons/ExpandLess'
import ExpandMore from '@material-ui/icons/ExpandMore'
import IconButton from '@material-ui/core/IconButton'
import ListItemText from '~/components/List/ListItemText'
import Row from '~/components/layout/Row'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import Avatar from '@material-ui/core/Avatar'
import AttachMoney from '@material-ui/icons/AttachMoney'
import Atm from '@material-ui/icons/LocalAtm'
import DoneAll from '@material-ui/icons/DoneAll'
import CompareArrows from '@material-ui/icons/CompareArrows'
import Collapsed from '~/routes/safe/components/Transactions/Collapsed'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import Hairline from '~/components/layout/Hairline/index'
import Button from '~/components/layout/Button'
import { sameAddress } from '~/logic/wallets/ethAddresses'
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
import selector, { type SelectorProps } from './selector'
type Props = Open &
SelectorProps & {
transaction: Transaction,
safeName: string,
threshold: number,
onProcessTx: (tx: Transaction, alreadyConfirmed: number) => void,
}
export const PROCESS_TXS = 'PROCESS TRANSACTION'
class GnoTransaction extends React.PureComponent<Props> {
onProccesClick = () => {
const { onProcessTx, transaction, confirmed } = this.props
onProcessTx(transaction, confirmed)
}
hasConfirmed = (userAddress: string, confirmations: List<Confirmation>): boolean => (
confirmations
.filter(
(conf: Confirmation) => (
sameAddress(userAddress, conf.get('owner').get('address')) && conf.get('type') === 'confirmation'
),
)
.count() > 0
)
render() {
const {
open, toggle, transaction, confirmed, safeName, userAddress, executionHash, threshold,
} = this.props
const confirmationText = executionHash
? 'Already executed'
: `${confirmed} of the ${threshold} confirmations needed`
const userConfirmed = this.hasConfirmed(userAddress, transaction.get('confirmations'))
return (
<React.Fragment>
<Row>
<ListItem onClick={toggle}>
<Avatar>
<Atm />
</Avatar>
<ListItemText primary="Tx Name" secondary={transaction.get('name')} />
<Avatar>
<AttachMoney />
</Avatar>
<ListItemText primary="Value" secondary={`${transaction.get('value')} ETH`} />
<Avatar>
<DoneAll />
</Avatar>
<ListItemText primary="Status" secondary={confirmationText} />
<ListItemIcon>
{open ? (
<IconButton disableRipple>
<ExpandLess />
</IconButton>
) : (
<IconButton disableRipple>
<ExpandMore />
</IconButton>
)}
</ListItemIcon>
</ListItem>
</Row>
<Row>
<ListItem>
{executionHash && (
<React.Fragment>
<Avatar>
<CompareArrows />
</Avatar>
<ListItemText cut primary="Transaction Hash" secondary={executionHash} />
</React.Fragment>
)}
{!executionHash && userConfirmed && (
<React.Fragment>
<Avatar>
<CompareArrows />
</Avatar>
<ListItemText cut primary="Confirmed" secondary="Waiting for the rest of confirmations" />
</React.Fragment>
)}
{!executionHash && !userConfirmed && (
<Button variant="contained" color="primary" onClick={this.onProccesClick}>
{PROCESS_TXS}
</Button>
)}
</ListItem>
</Row>
{open && (
<Collapsed
safeName={safeName}
confirmations={transaction.get('confirmations')}
destination={transaction.get('destination')}
threshold={threshold}
/>
)}
<Hairline margin="md" />
</React.Fragment>
)
}
}
export default openHoc(connect(selector)(GnoTransaction))

View File

@ -1,34 +0,0 @@
// @flow
import { createStructuredSelector } from 'reselect'
import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index'
import { userAccountSelector } from '~/logic/wallets/store/selectors'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { type GlobalState } from '~/store'
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
export type SelectorProps = {
confirmed: typeof confirmationsTransactionSelector,
userAddress: typeof userAccountSelector,
executionHash: string,
}
type TxProps = {
transaction: Transaction,
}
const transactionHashSector = (state: GlobalState, props: TxProps) => {
if (!props.transaction) {
return undefined
}
const confirmations = props.transaction.get('confirmations')
const executedConf = confirmations.find((conf: Confirmation) => conf.get('type') === 'execution')
return executedConf ? executedConf.get('hash') : undefined
}
export default createStructuredSelector<Object, *>({
executionHash: transactionHashSector,
confirmed: confirmationsTransactionSelector,
userAddress: userAccountSelector,
})

View File

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

View File

@ -1,65 +1,58 @@
// @flow
import * as React from 'react'
import React, { useEffect } from 'react'
import { List } from 'immutable'
import { connect } from 'react-redux'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import NoTransactions from '~/routes/safe/components/Transactions/NoTransactions'
import GnoTransaction from '~/routes/safe/components/Transactions/Transaction'
import { sameAddress } from '~/logic/wallets/ethAddresses'
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
import { processTransaction } from '~/logic/safe/safeFrontendOperations'
import selector, { type SelectorProps } from './selector'
import actions, { type Actions } from './actions'
import TxsTable from '~/routes/safe/components/Transactions/TxsTable'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { type Owner } from '~/routes/safe/store/models/owner'
type Props = SelectorProps & Actions & {
safeName: string,
type Props = {
safeAddress: string,
threshold: number,
}
class Transactions extends React.Component<Props, {}> {
componentDidMount() {
const { fetchTransactions, safeAddress } = this.props
fetchTransactions(safeAddress)
}
onProcessTx = async (tx: Transaction, alreadyConfirmed: number) => {
const {
fetchTransactions, safeAddress, userAddress, threshold,
} = this.props
const confirmations = tx.get('confirmations')
const usersConfirmed = List(confirmations.map((confirmation: Confirmation) =>
confirmation.get('owner').get('address')))
const userHasAlreadyConfirmed = confirmations.filter((confirmation: Confirmation) => {
const ownerAddress = confirmation.get('owner').get('address')
const samePerson = sameAddress(ownerAddress, userAddress)
return samePerson && confirmation.get('type') === 'confirmation'
}).count() > 0
if (userHasAlreadyConfirmed) {
throw new Error('Owner has already confirmed this transaction')
}
await processTransaction(safeAddress, tx, alreadyConfirmed, userAddress, threshold, usersConfirmed)
fetchTransactions(safeAddress)
}
render() {
const { transactions, safeName, threshold } = this.props
const hasTransactions = transactions.count() > 0
return (
<React.Fragment>
{ hasTransactions
? transactions.map((tx: Transaction) => <GnoTransaction key={tx.get('nonce')} safeName={safeName} onProcessTx={this.onProcessTx} transaction={tx} threshold={threshold} />)
: <NoTransactions />
}
</React.Fragment>
)
}
fetchTransactions: Function,
transactions: List<Transaction>,
owners: List<Owner>,
userAddress: string,
granted: boolean,
createTransaction: Function,
processTransaction: Function,
}
export default connect(selector, actions)(Transactions)
const Transactions = ({
transactions = List(),
owners,
threshold,
userAddress,
granted,
safeAddress,
createTransaction,
processTransaction,
fetchTransactions,
}: Props) => {
useEffect(() => {
fetchTransactions(safeAddress)
}, [safeAddress])
const hasTransactions = transactions.size > 0
return (
<>
{hasTransactions ? (
<TxsTable
transactions={transactions}
threshold={threshold}
owners={owners}
userAddress={userAddress}
granted={granted}
safeAddress={safeAddress}
createTransaction={createTransaction}
processTransaction={processTransaction}
/>
) : (
<NoTransactions />
)}
</>
)
}
export default Transactions

View File

@ -1,16 +0,0 @@
// @flow
import { List } from 'immutable'
import { createStructuredSelector } from 'reselect'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
import { userAccountSelector } from '~/logic/wallets/store/selectors'
export type SelectorProps = {
transactions: List<Transaction>,
userAddress: typeof userAccountSelector,
}
export default createStructuredSelector<Object, *>({
transactions: safeTransactionsSelector,
userAddress: userAccountSelector,
})

View File

@ -1,20 +0,0 @@
// @flow
import * as React from 'react'
import Bold from '~/components/layout/Bold'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph/index'
export const NO_TRANSACTION_ROW_TEST_ID = 'no-transaction-row'
const NoTransactions = () => (
<Row data-testid={NO_TRANSACTION_ROW_TEST_ID}>
<Col xs={12} center="xs" sm={10} smOffset={2} start="sm" margin="md">
<Paragraph size="lg">
<Bold>No transactions found for this safe</Bold>
</Paragraph>
</Col>
</Row>
)
export default NoTransactions

View File

@ -1,58 +0,0 @@
// @flow
import React, { useEffect } from 'react'
import { List } from 'immutable'
import NoTransactions from '~/routes/safe/components/TransactionsNew/NoTransactions'
import TxsTable from '~/routes/safe/components/TransactionsNew/TxsTable'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { type Owner } from '~/routes/safe/store/models/owner'
type Props = {
safeAddress: string,
threshold: number,
fetchTransactions: Function,
transactions: List<Transaction>,
owners: List<Owner>,
userAddress: string,
granted: boolean,
createTransaction: Function,
processTransaction: Function,
}
const Transactions = ({
transactions = List(),
owners,
threshold,
userAddress,
granted,
safeAddress,
createTransaction,
processTransaction,
fetchTransactions,
}: Props) => {
useEffect(() => {
fetchTransactions(safeAddress)
}, [safeAddress])
const hasTransactions = transactions.size > 0
return (
<React.Fragment>
{hasTransactions ? (
<TxsTable
transactions={transactions}
threshold={threshold}
owners={owners}
userAddress={userAddress}
granted={granted}
safeAddress={safeAddress}
createTransaction={createTransaction}
processTransaction={processTransaction}
/>
) : (
<NoTransactions />
)}
</React.Fragment>
)
}
export default Transactions

View File

@ -5,7 +5,13 @@ import { userAccountSelector } from '~/logic/wallets/store/selectors'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { type GlobalState } from '~/store'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { approveTransaction, executeTransaction, CALL } from '~/logic/safe/transactions'
import {
approveTransaction,
executeTransaction,
CALL,
type Notifications,
DEFAULT_NOTIFICATIONS,
} from '~/logic/safe/transactions'
const createTransaction = (
safeAddress: string,
@ -14,6 +20,7 @@ const createTransaction = (
txData: string = EMPTY_DATA,
openSnackbar: Function,
shouldExecute?: boolean,
notifications?: Notifications = DEFAULT_NOTIFICATIONS,
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
const state: GlobalState = getState()
@ -24,14 +31,19 @@ const createTransaction = (
const isExecution = threshold.toNumber() === 1 || shouldExecute
let txHash
if (isExecution) {
openSnackbar('Transaction has been submitted', 'success')
txHash = await executeTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
openSnackbar('Transaction has been confirmed', 'success')
} else {
openSnackbar('Approval transaction has been submitted', 'success')
txHash = await approveTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
openSnackbar('Approval transaction has been confirmed', 'success')
try {
if (isExecution) {
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
txHash = await executeTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
openSnackbar(notifications.AFTER_EXECUTION, 'success')
} else {
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
txHash = await approveTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
openSnackbar(notifications.CREATED_MORE_CONFIRMATIONS_NEEDED, 'success')
}
} catch (err) {
openSnackbar(notifications.ERROR, '')
console.error(`Error while creating transaction: ${err}`)
}
dispatch(fetchTransactions(safeAddress))

View File

@ -10,10 +10,10 @@ import '@testing-library/jest-dom/extend-expect'
import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances'
import { fillAndSubmitSendFundsForm } from './utils/transactions'
import { TRANSACTIONS_TAB_BTN_TEST_ID } from '~/routes/safe/components/Layout'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable'
import { useTestAccountAt, resetTestAccount } from './utils/accounts'
import { CONFIRM_TX_BTN_TEST_ID, EXECUTE_TX_BTN_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable/ExpandedTx/ApproveTxModal'
import { CONFIRM_TX_BTN_TEST_ID, EXECUTE_TX_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal'
afterEach(resetTestAccount)

View File

@ -3,12 +3,12 @@ import { fireEvent } from '@testing-library/react'
import { sleep } from '~/utils/timer'
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
import { TRANSACTIONS_TAB_BTN_TEST_ID } from '~/routes/safe/components/Layout'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/TransactionsNew/TxsTable'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable'
import {
TRANSACTIONS_DESC_ADD_OWNER_TEST_ID,
TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID,
TRANSACTIONS_DESC_SEND_TEST_ID,
} from '~/routes/safe/components/TransactionsNew/TxsTable/ExpandedTx/TxDescription'
} from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription'
export const getLastTransaction = async (SafeDom: React.Component<any, any>) => {
// Travel to transactions

View File

@ -15,6 +15,7 @@ import {
regularFont,
boldFont,
buttonLargeFontSize,
largeFontSize,
xs,
secondaryText,
} from './variables'
@ -48,6 +49,7 @@ export default createMuiTheme({
MuiButton: {
label: {
lineHeight: 1,
fontSize: largeFontSize,
fontWeight: regularFont,
},
root: {
@ -61,7 +63,7 @@ export default createMuiTheme({
borderRadius: '8px',
},
contained: {
boxShadow: 'none',
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
},
containedPrimary: {
backgroundColor: secondary,

374
yarn.lock
View File

@ -2,10 +2,10 @@
# yarn lockfile v1
"@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==
"@babel/cli@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.0.tgz#1470a04394eaf37862989ea4912adf440fa6ff8d"
integrity sha512-1CTDyGUjQqW3Mz4gfKZ04KGOckyyaNmKneAMlABPS+ZyuxWv3FrVEVz7Ag08kNIztVx8VaJ8YgvYLSNlMKAT5Q==
dependencies:
commander "^2.8.1"
convert-source-map "^1.1.0"
@ -17,7 +17,7 @@
slash "^2.0.0"
source-map "^0.5.0"
optionalDependencies:
chokidar "^2.0.4"
chokidar "^2.1.8"
"@babel/code-frame@7.0.0":
version "7.0.0"
@ -53,7 +53,27 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/core@7.5.5", "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.4.5":
"@babel/core@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.0.tgz#9b00f73554edd67bebc86df8303ef678be3d7b48"
integrity sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.0"
"@babel/helpers" "^7.6.0"
"@babel/parser" "^7.6.0"
"@babel/template" "^7.6.0"
"@babel/traverse" "^7.6.0"
"@babel/types" "^7.6.0"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
lodash "^4.17.13"
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":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30"
integrity sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==
@ -84,6 +104,17 @@
source-map "^0.5.0"
trim-right "^1.0.1"
"@babel/generator@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.0.tgz#e2c21efbfd3293ad819a2359b448f002bfdfda56"
integrity sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==
dependencies:
"@babel/types" "^7.6.0"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.5.0"
trim-right "^1.0.1"
"@babel/helper-annotate-as-pure@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
@ -116,7 +147,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.5":
"@babel/helper-create-class-features-plugin@^7.4.0", "@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==
@ -128,6 +159,18 @@
"@babel/helper-replace-supers" "^7.5.5"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/helper-create-class-features-plugin@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz#769711acca889be371e9bc2eb68641d55218021f"
integrity sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-member-expression-to-functions" "^7.5.5"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-replace-supers" "^7.5.5"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/helper-define-map@^7.4.0", "@babel/helper-define-map@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369"
@ -268,6 +311,15 @@
"@babel/traverse" "^7.5.5"
"@babel/types" "^7.5.5"
"@babel/helpers@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.0.tgz#21961d16c6a3c3ab597325c34c465c0887d31c6e"
integrity sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ==
dependencies:
"@babel/template" "^7.6.0"
"@babel/traverse" "^7.6.0"
"@babel/types" "^7.6.0"
"@babel/highlight@^7.0.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540"
@ -282,6 +334,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b"
integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==
"@babel/parser@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.0.tgz#3e05d0647432a8326cb28d0de03895ae5a57f39b"
integrity sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e"
@ -316,19 +373,19 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-decorators" "^7.2.0"
"@babel/plugin-proposal-decorators@7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz#de9b2a1a8ab0196f378e2a82f10b6e2a36f21cc0"
integrity sha512-z7MpQz3XC/iQJWXH9y+MaWcLPNSMY9RQSthrLzak8R8hCj0fuyNk+Dzi9kfNe/JxxlWQ2g7wkABbgWjW36MTcw==
"@babel/plugin-proposal-decorators@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.6.0.tgz#6659d2572a17d70abd68123e89a12a43d90aa30c"
integrity sha512-ZSyYw9trQI50sES6YxREXKu+4b7MAg6Qx2cvyDDYjP2Hpzd3FleOUwC9cqn1+za8d0A2ZU8SHujxFao956efUg==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.4.4"
"@babel/helper-create-class-features-plugin" "^7.6.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-decorators" "^7.2.0"
"@babel/plugin-proposal-do-expressions@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-do-expressions/-/plugin-proposal-do-expressions-7.5.0.tgz#ceb594d4a618545b00aa0b5cd61cad4aaaeb7a5a"
integrity sha512-xe0QQrhm+DGj6H23a6XtwkJNimy1fo71O/YVBfrfvfSl0fsq9T9dfoQBIY4QceEIdUo7u9s7OPEdsWEuizfGeg==
"@babel/plugin-proposal-do-expressions@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-do-expressions/-/plugin-proposal-do-expressions-7.6.0.tgz#192953fed8620d13d12a61f68defd26f41059193"
integrity sha512-qJDaoBDbLySwU1tG0jbAomOwz8W1PEiiiK0iLQAnHLr4PYIMVX4ltDGkj3uAKx4HDs1WJ0tozGW1zAQjuTIiWg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-do-expressions" "^7.2.0"
@ -430,10 +487,10 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-proposal-optional-chaining@^7.0.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.2.0.tgz#ae454f4c21c6c2ce8cb2397dc332ae8b420c5441"
integrity sha512-ea3Q6edZC/55wEBVZAEz42v528VulyO0eir+7uky/sT4XRcdkWJcFi1aPtitTlwUzGnECWJNExWww1SStt+yWw==
"@babel/plugin-proposal-optional-chaining@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.6.0.tgz#e9bf1f9b9ba10c77c033082da75f068389041af8"
integrity sha512-kj4gkZ6qUggkprRq3Uh5KP8XnE1MdIO0J7MhdDX8+rAbB6dJ2UrensGIS+0NPZAaaJ1Vr0PN6oLUgXMU1uMcSg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-chaining" "^7.2.0"
@ -641,6 +698,14 @@
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13"
"@babel/plugin-transform-block-scoping@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.0.tgz#c49e21228c4bbd4068a35667e6d951c75439b1dc"
integrity sha512-tIt4E23+kw6TgL/edACZwP1OUKrjOTyMrFMLoT5IOFrfMRabCgekjqFd5o6PaAMildBu46oFkekIdMuGkkPEpA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13"
"@babel/plugin-transform-classes@7.4.3":
version "7.4.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.3.tgz#adc7a1137ab4287a555d429cc56ecde8f40c062c"
@ -690,6 +755,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-destructuring@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6"
integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-dotall-regex@^7.4.3", "@babel/plugin-transform-dotall-regex@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3"
@ -778,6 +850,16 @@
"@babel/helper-simple-access" "^7.1.0"
babel-plugin-dynamic-import-node "^2.3.0"
"@babel/plugin-transform-modules-commonjs@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz#39dfe957de4420445f1fcf88b68a2e4aa4515486"
integrity sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==
dependencies:
"@babel/helper-module-transforms" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-simple-access" "^7.1.0"
babel-plugin-dynamic-import-node "^2.3.0"
"@babel/plugin-transform-modules-systemjs@^7.4.0", "@babel/plugin-transform-modules-systemjs@^7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249"
@ -802,6 +884,13 @@
dependencies:
regexp-tree "^0.1.6"
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.0.tgz#1e6e663097813bb4f53d42df0750cf28ad3bb3f1"
integrity sha512-jem7uytlmrRl3iCAuQyw8BpB4c4LWvSpvIeXKpMb+7j84lkx4m4mYr5ErAcmN5KM7B6BqrAvRGjBIbbzqCczew==
dependencies:
regexp-tree "^0.1.13"
"@babel/plugin-transform-new-target@^7.4.0", "@babel/plugin-transform-new-target@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5"
@ -960,10 +1049,10 @@
"@babel/helper-regex" "^7.4.4"
regexpu-core "^4.5.4"
"@babel/polyfill@7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.4.4.tgz#78801cf3dbe657844eeabf31c1cae3828051e893"
integrity sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==
"@babel/polyfill@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.6.0.tgz#6d89203f8b6cd323e8d946e47774ea35dc0619cc"
integrity sha512-q5BZJI0n/B10VaQQvln1IlDK3BTBJFbADx7tv+oXDPIDZuTo37H5Adb9jhlXm/fEN4Y7/64qD9mnrJJG7rmaTw==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
@ -1022,7 +1111,63 @@
js-levenshtein "^1.1.3"
semver "^5.5.0"
"@babel/preset-env@7.5.5", "@babel/preset-env@^7.4.5":
"@babel/preset-env@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.0.tgz#aae4141c506100bb2bfaa4ac2a5c12b395619e50"
integrity sha512-1efzxFv/TcPsNXlRhMzRnkBFMeIqBBgzwmZwlFDw5Ubj0AGLeufxugirwZmkkX/ayi3owsSqoQ4fw8LkfK9SYg==
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.5"
"@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.6.0"
"@babel/plugin-transform-classes" "^7.5.5"
"@babel/plugin-transform-computed-properties" "^7.2.0"
"@babel/plugin-transform-destructuring" "^7.6.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.6.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.6.0"
"@babel/plugin-transform-new-target" "^7.4.4"
"@babel/plugin-transform-object-super" "^7.5.5"
"@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.6.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":
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==
@ -1143,6 +1288,15 @@
"@babel/parser" "^7.4.4"
"@babel/types" "^7.4.4"
"@babel/template@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.6.0"
"@babel/types" "^7.6.0"
"@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"
@ -1158,6 +1312,21 @@
globals "^11.1.0"
lodash "^4.17.13"
"@babel/traverse@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.0.tgz#389391d510f79be7ce2ddd6717be66d3fed4b516"
integrity sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.0"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/parser" "^7.6.0"
"@babel/types" "^7.6.0"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
@ -1167,6 +1336,15 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@babel/types@^7.6.0":
version "7.6.1"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648"
integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==
dependencies:
esutils "^2.0.2"
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@cnakazawa/watch@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@ -1518,13 +1696,13 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"
"@material-ui/core@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.4.0.tgz#820b6ee91da9eaf4018ff9e87e6ca04fc985e35a"
integrity sha512-p6yDcLYYHzJD0A6im+prcCahZBdlhh2OrTGQ4W1XN1X5uiMIg4niJ8FYBpgV0ssaluO+EYlaAKp2qGeMNRr/bA==
"@material-ui/core@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.4.1.tgz#cc9b8e417ced1ab3145fdeda41a6aee657d3524b"
integrity sha512-LotIIGv8EDMj6mwXobsMF4WlCe+PtMjrXa34U2B0xFMdZLrNYwdOHFgkAIaE0m/ibMXTobNKWqhc5bhXLxvXoQ==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/styles" "^4.3.3"
"@material-ui/styles" "^4.4.1"
"@material-ui/system" "^4.3.3"
"@material-ui/types" "^4.1.1"
"@material-ui/utils" "^4.4.0"
@ -1540,17 +1718,17 @@
react-transition-group "^4.0.0"
warning "^4.0.1"
"@material-ui/icons@4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.2.1.tgz#fe2f1c4f60c24256d244a69d86d0c00e8ed4037e"
integrity sha512-FvSD5lUBJ66frI4l4AYAPy2CH14Zs2Dgm0o3oOMr33BdQtOAjCgbdOcvPBeaD1w6OQl31uNW3CKOE8xfPNxvUQ==
"@material-ui/icons@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.4.1.tgz#a09d53275a5d73a77ee621f91c005a1793432df7"
integrity sha512-ilo6rSgsI3B5L7s1H3tBS1x77sJqPql3QJDYzVi2TVluZLCTfRKjpbYYDUwolEAQC0MzXLjVbXHqJ97VeJqpmQ==
dependencies:
"@babel/runtime" "^7.2.0"
"@babel/runtime" "^7.4.4"
"@material-ui/styles@^4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.3.3.tgz#39867dd6f3779a8326075e097d72208d3c5b4977"
integrity sha512-quupQ6RYXbtKBJxhLkF3RQx6LSfrfuh2lYpILvk7p9XNkfqOQq36fuNVgrJ/A+NNn03uqDFfQYIWh4CByKr4hA==
"@material-ui/styles@^4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.4.1.tgz#a53fb39e373636bd2c296a78c54afecb80f68446"
integrity sha512-wXASlta7G+N8NeihbAKQjL6E1XMkS3SWpksNKn1cxhmKYQ+5pkMAUW/rChC5ovG7C/C2ZIdajYgOz977m3xlBA==
dependencies:
"@babel/runtime" "^7.4.4"
"@emotion/hash" "^0.7.1"
@ -2255,13 +2433,14 @@
dependencies:
defer-to-connect "^1.0.1"
"@testing-library/dom@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.0.0.tgz#34e28e69e49bd6347fc64a5dde4c4f9aabbd17d3"
integrity sha512-B5XTz3uMsbqbdR9CZlnwpZjTE3fCWuqRkz/zvDc2Ej/vuHmTM0Ur2v0XPwr7usWfGIBsahEK5HL1E91+4IFiBg==
"@testing-library/dom@^6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.1.0.tgz#8d5a954158e81ecd7c994907f4ec240296ed823b"
integrity sha512-qivqFvnbVIH3DyArFofEU/jlOhkGIioIemOy9A9M/NQTpPyDDQmtVkAfoB18RKN581f0s/RJMRBbq9WfMIhFTw==
dependencies:
"@babel/runtime" "^7.5.5"
"@sheerun/mutationobserver-shim" "^0.3.2"
"@types/testing-library__dom" "^6.0.0"
aria-query "3.0.0"
pretty-format "^24.8.0"
wait-for-expect "^1.3.0"
@ -2281,13 +2460,13 @@
pretty-format "^24.0.0"
redent "^3.0.0"
"@testing-library/react@9.1.3":
version "9.1.3"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.1.3.tgz#3fb495227322ea36cd817532441dabb552e0d6ce"
integrity sha512-qFVo6TsEbpEFpOmKjIxMHDujOKVdvVpcYFcUfJeWBqMO8eja5pN9SZnt6W6AzW3a1MRvRfw3X0Fhx3eXnBJxjA==
"@testing-library/react@9.1.4":
version "9.1.4"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-9.1.4.tgz#4cc1a228a944c0f468ee501e7da1651d8bbd9902"
integrity sha512-fQ/PXZoLcmnS1W5ZiM3P7XBy2x6Hm9cJAT/ZDuZKzJ1fS1rN3j31p7ReAqUe3N1kJ46sNot0n1oiGbz7FPU+FA==
dependencies:
"@babel/runtime" "^7.5.5"
"@testing-library/dom" "^6.0.0"
"@testing-library/dom" "^6.1.0"
"@types/testing-library__react" "^9.1.0"
"@truffle/blockchain-utils@^0.0.11":
@ -2462,6 +2641,13 @@
dependencies:
"@types/pretty-format" "*"
"@types/testing-library__dom@^6.0.0":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-6.0.2.tgz#1e40c5f12671b98e86706f6c9ba55eaab3461edc"
integrity sha512-p5Jjm2kaHpee2rMartE3F2vg4rosMoAc1KVr4+WPz1TOc4B8A6EO5oLJUZvuRyiT3pFy/WO77kn40qp51DF3Sg==
dependencies:
pretty-format "^24.3.0"
"@types/testing-library__react@^9.1.0":
version "9.1.1"
resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.1.tgz#4bcb8bba54b07fbb6c084f2f00e7f9410e587c10"
@ -5178,6 +5364,25 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.6:
optionalDependencies:
fsevents "^1.2.7"
chokidar@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
dependencies:
anymatch "^2.0.0"
async-each "^1.0.1"
braces "^2.3.2"
glob-parent "^3.1.0"
inherits "^2.0.3"
is-binary-path "^1.0.0"
is-glob "^4.0.0"
normalize-path "^3.0.0"
path-is-absolute "^1.0.0"
readdirp "^2.2.1"
upath "^1.1.1"
optionalDependencies:
fsevents "^1.2.7"
chownr@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6"
@ -6147,10 +6352,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
date-fns@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.1.tgz#c5f30e31d3294918e6b6a82753a4e719120e203d"
integrity sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==
date-fns@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.1.0.tgz#0d7e806c3cefe14a943532dbf968995ccfd46bd9"
integrity sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA==
date-now@^0.1.4:
version "0.1.4"
@ -6997,10 +7202,10 @@ eslint-module-utils@^2.4.0:
debug "^2.6.8"
pkg-dir "^2.0.0"
eslint-plugin-flowtype@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.2.0.tgz#a89ac991eef6753226eb8871261e266645aca4b9"
integrity sha512-mqf6AbQCP6N8Bk+ryXYwxt6sj3RT7i3kt8JOOx7WOQNlZtsLxqvnkXRRrToFHcN52E5W9c/p3UfNxCMsfENIJA==
eslint-plugin-flowtype@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.3.0.tgz#06d0837ac341caf369e7e6dbb112dd7fd21acf17"
integrity sha512-elvqoadMHnYqSYN1YXn02DR7SFW8Kc2CLe8na3m2GdQPQhIY+BgCd2quVJ1AbW3aO0zcyE9loVJ7Szy8A/xlMA==
dependencies:
lodash "^4.17.15"
@ -7021,10 +7226,10 @@ eslint-plugin-import@2.18.2:
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-jest@22.16.0:
version "22.16.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.16.0.tgz#30c4e0e9dc331beb2e7369b70dd1363690c1ce05"
integrity sha512-eBtSCDhO1k7g3sULX/fuRK+upFQ7s548rrBtxDyM1fSoY7dTWp/wICjrJcDZKVsW7tsFfH22SG+ZaxG5BZodIg==
eslint-plugin-jest@22.17.0:
version "22.17.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz#dc170ec8369cd1bff9c5dd8589344e3f73c88cf6"
integrity sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q==
dependencies:
"@typescript-eslint/experimental-utils" "^1.13.0"
@ -7330,18 +7535,17 @@ ethereum-common@^0.0.18:
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f"
integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=
ethereum-ens@^0.7.7:
version "0.7.7"
resolved "https://registry.yarnpkg.com/ethereum-ens/-/ethereum-ens-0.7.7.tgz#43e104552b9ad70b232a5d98b26367604b4e055c"
integrity sha512-KawlKV0CD8cj3KHIij9xuTpqCMjBjbjBrqJ0xcKNCuaWNN6CtqHoHqgPcVoiad0TuomqzXjzujBOvuacm2Bq4w==
ethereum-ens@0.7.8:
version "0.7.8"
resolved "https://registry.yarnpkg.com/ethereum-ens/-/ethereum-ens-0.7.8.tgz#102874541801507774fef21c9a626fabb1ed0630"
integrity sha512-HJBDmF5/abP/IIM6N7rGHmmlQ4yCKIVK4kzT/Mu05+eZn0i5ZlR25LTAE47SVZ7oyTBvOkNJhxhSkWRvjh7srg==
dependencies:
bluebird "^3.4.7"
eth-ens-namehash "^2.0.0"
js-sha3 "^0.5.7"
pako "^1.0.4"
text-encoding "^0.6.4"
underscore "^1.8.3"
web3 "1.0.0-beta.37"
web3 "^1.0.0-beta.34"
ethereumjs-abi@0.6.5:
version "0.6.5"
@ -8217,10 +8421,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.106.3:
version "0.106.3"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.106.3.tgz#87b5647bc23ae0efceabb6c50490c02a8478960c"
integrity sha512-QDwmhsMmiASmwgr6r2WTz9RPsN0pb84PY0whz0JqFaBX7/Fx2wj2MOtjbR2yv+qWZnozP9U40Jd9LLt8rC3WSQ==
flow-bin@0.107.0:
version "0.107.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.107.0.tgz#b37bfcce51204d35d58f8eb93b3a76b52291e4cc"
integrity sha512-hsmwO5Q0+XUXaO2kIKLpleUNNBSFcsGEQGBOTEC/KR/4Ez695I1fweX/ioSjbU4RWhPZhkIqnpbF9opVAauCHg==
flow-stoplight@^1.0.0:
version "1.0.0"
@ -13860,7 +14064,7 @@ pretty-format@^24.0.0, pretty-format@^24.8.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
pretty-format@^24.9.0:
pretty-format@^24.3.0, pretty-format@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
@ -14973,6 +15177,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexp-tree@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.13.tgz#5b19ab9377edc68bc3679256840bb29afc158d7f"
integrity sha512-hwdV/GQY5F8ReLZWO+W1SRoN5YfpOKY6852+tBFcma72DKBIcHjPRIlIvQN35bCOljuAfP2G2iB0FC/w236mUw==
regexp-tree@^0.1.6:
version "0.1.11"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.11.tgz#c9c7f00fcf722e0a56c7390983a7a63dd6c272f3"
@ -16783,11 +16992,6 @@ test-exclude@^5.2.3:
read-pkg-up "^4.0.0"
require-main-filename "^2.0.0"
text-encoding@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk=
text-table@0.2.0, text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -17537,10 +17741,10 @@ truffle-workflow-compile@^2.1.3:
truffle-external-compile "^1.0.15"
truffle-resolver "^5.0.15"
truffle@5.0.34:
version "5.0.34"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.34.tgz#f2b667843418002511f5604254ed365ebdfa9534"
integrity sha512-fSA3JjaIjFrgn4BGfoATg2ATVWS51240L8mEQdtqLUncOcFnMLTBEaVRujO/f97XW+ew0hUg13oS4H/2Z4dwtg==
truffle@5.0.35:
version "5.0.35"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.35.tgz#5c93522c3a0915567b2e5c7811934e0ee6e8a2bd"
integrity sha512-ewJPaeHyYgRpuVSvlzhlnalJkeLN0sz7c/P/8WLWpXC966M2o4vL5ov6MNdSHQFYiYQsDrCetrothzsYsg4HWQ==
dependencies:
app-module-path "^2.2.0"
mocha "5.2.0"
@ -19183,7 +19387,7 @@ web3@1.0.0-beta.37:
web3-shh "1.0.0-beta.37"
web3-utils "1.0.0-beta.37"
web3@1.2.1:
web3@1.2.1, web3@^1.0.0-beta.34:
version "1.2.1"
resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.1.tgz#5d8158bcca47838ab8c2b784a2dee4c3ceb4179b"
integrity sha512-nNMzeCK0agb5i/oTWNdQ1aGtwYfXzHottFP2Dz0oGIzavPMGSKyVlr8ibVb1yK5sJBjrWVnTdGaOC2zKDFuFRw==
@ -19272,10 +19476,10 @@ webpack-bundle-analyzer@3.4.1:
opener "^1.5.1"
ws "^6.0.0"
webpack-cli@3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.7.tgz#77c8580dd8e92f69d635e0238eaf9d9c15759a91"
integrity sha512-OhTUCttAsr+IZSMVwGROGRHvT+QAs8H6/mHIl4SvhAwYywjiylYjpwybGx7WQ9Hkb45FhjtsymkwiRRbGJ1SZQ==
webpack-cli@3.3.8:
version "3.3.8"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.8.tgz#caeaebcc26f685db1736e5decd3f01aac30123ec"
integrity sha512-RANYSXwikSWINjHMd/mtesblNSpjpDLoYTBtP99n1RhXqVI/wxN40Auqy42I7y4xrbmRBoA5Zy5E0JSBD5XRhw==
dependencies:
chalk "2.4.2"
cross-spawn "6.0.5"