diff --git a/readme.md b/readme.md index bb883ae2..9746885e 100644 --- a/readme.md +++ b/readme.md @@ -102,9 +102,7 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available, ## Authors -* **Adolfo Panizo** - [apanizo](https://github.com/apanizo) - -See also the list of [contributors](https://github.com/gnosis/gnosis-team-safe/contributors) who participated in this project. +See the list of [contributors](https://github.com/gnosis/gnosis-team-safe/contributors) who participated in this project. ## License diff --git a/src/components/Footer/index.stories.js b/src/components/Footer/index.stories.js index f3220920..e0b29e91 100644 --- a/src/components/Footer/index.stories.js +++ b/src/components/Footer/index.stories.js @@ -4,7 +4,7 @@ import * as React from 'react' import styles from '~/components/layout/PageFrame/index.scss' import Component from './index' -const FrameDecorator = story => ( +const FrameDecorator = (story) => (
{story()} diff --git a/src/components/Header/component/CircleDot.jsx b/src/components/Header/component/CircleDot.jsx index 32ff7f54..7c54d1e2 100644 --- a/src/components/Header/component/CircleDot.jsx +++ b/src/components/Header/component/CircleDot.jsx @@ -77,7 +77,7 @@ const KeyRing = ({ const img = isWarning ? triangle : key return ( - + <> {!hideDot && } - + ) } diff --git a/src/components/Header/component/Layout.stories.js b/src/components/Header/component/Layout.stories.js index 7d0ccbe3..e8b235b7 100644 --- a/src/components/Header/component/Layout.stories.js +++ b/src/components/Header/component/Layout.stories.js @@ -8,7 +8,7 @@ import UserDetails from './ProviderDetails/UserDetails' import ProviderDisconnected from './ProviderInfo/ProviderDisconnected' import ConnectDetails from './ProviderDetails/ConnectDetails' -const FrameDecorator = story =>
{story()}
+const FrameDecorator = (story) =>
{story()}
storiesOf('Components /Header', module) .addDecorator(FrameDecorator) diff --git a/src/components/Loader/indes.stories.js b/src/components/Loader/indes.stories.js index f6576f3b..314b33d1 100644 --- a/src/components/Loader/indes.stories.js +++ b/src/components/Loader/indes.stories.js @@ -4,7 +4,7 @@ import * as React from 'react' import styles from '~/components/layout/PageFrame/index.scss' import Component from './index' -const FrameDecorator = story =>
{story()}
+const FrameDecorator = (story) =>
{story()}
storiesOf('Components', module) .addDecorator(FrameDecorator) diff --git a/src/components/Table/sorting.js b/src/components/Table/sorting.js index aad73260..e53480b4 100644 --- a/src/components/Table/sorting.js +++ b/src/components/Table/sorting.js @@ -39,7 +39,7 @@ export const stableSort = (dataArray: List, cmp: any, fixed: boolean): List return a[1] - b[1] }) - const sortedElems: List = stabilizedThis.map(el => el[0]) + const sortedElems: List = stabilizedThis.map((el) => el[0]) return fixedElems.concat(sortedElems) } diff --git a/src/components/forms/AddressInput/index.jsx b/src/components/forms/AddressInput/index.jsx index 71217c95..1ad3ea3a 100644 --- a/src/components/forms/AddressInput/index.jsx +++ b/src/components/forms/AddressInput/index.jsx @@ -7,6 +7,7 @@ import { composeValidators, required, mustBeEthereumAddress, + mustBeEthereumContractAddress, } from '~/components/forms/validator' import { getAddressFromENS } from '~/logic/wallets/getWeb3' @@ -19,6 +20,7 @@ type Props = { testId?: string, validators?: Function[], inputAdornment?: React.Element, + mustBeContract?: boolean, } const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name) @@ -35,6 +37,7 @@ const AddressInput = ({ testId, inputAdornment, validators = [], + mustBeContract, }: Props): React.Element<*> => ( <> { inputAdornment, classes, testId, + rows, + multiline, ...rest } = this.props const helperText = value ? text : undefined @@ -53,6 +55,8 @@ class TextField extends React.PureComponent { onChange={onChange} value={value} // data-testid={testId} + rows={rows} + multiline={multiline} /> ) } diff --git a/src/components/forms/TextareaField/index.jsx b/src/components/forms/TextareaField/index.jsx new file mode 100644 index 00000000..741b793d --- /dev/null +++ b/src/components/forms/TextareaField/index.jsx @@ -0,0 +1,35 @@ +// @flow +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import { TextFieldProps } from '@material-ui/core/TextField' +import Field from '~/components/forms/Field' +import TextField from '~/components/forms/TextField' + +const styles = () => ({ + textarea: { + '& > div': { + height: '140px', + paddingTop: '0', + paddingBottom: '0', + alignItems: 'auto', + '& > textarea': { + fontSize: '15px', + letterSpacing: '-0.5px', + lineHeight: '20px', + height: '102px', + }, + }, + }, +}) + +const TextareaField = ({ classes, ...props }: TextFieldProps) => ( + +) + +export default withStyles(styles)(TextareaField) diff --git a/src/components/forms/validator.js b/src/components/forms/validator.js index e41ff0ef..f8bfda94 100644 --- a/src/components/forms/validator.js +++ b/src/components/forms/validator.js @@ -22,7 +22,7 @@ export const required = simpleMemoize((value: Field) => (value ? undefined : 'Re export const mustBeInteger = (value: string) => (!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined) -export const mustBeFloat = (value: number) => (Number.isNaN(Number(value)) ? 'Must be a number' : undefined) +export const mustBeFloat = (value: number) => (value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined) export const greaterThan = (min: number) => (value: string) => { if (Number.isNaN(Number(value)) || Number.parseFloat(value) > Number(min)) { @@ -66,6 +66,14 @@ export const mustBeEthereumAddress = simpleMemoize((address: Field) => { return isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name' }) +export const mustBeEthereumContractAddress = simpleMemoize(async (address: string) => { + const contractCode: string = await getWeb3().eth.getCode(address) + + return !contractCode || contractCode.replace('0x', '').replace(/0/g, '') === '' + ? 'Address should be a valid Ethereum contract address or ENS name' + : undefined +}) + export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`) export const ADDRESS_REPEATED_ERROR = 'Address already introduced' @@ -75,7 +83,7 @@ export const uniqueAddress = (addresses: string[] | List) => simpleMemoi return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined }) -export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => validators.reduce((error, validator) => error || validator(value), undefined) +export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => validators.reduce((error, validator) => (error || (validator && validator(value)), undefined)) export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (value: string) => { const amount = Number(value) diff --git a/src/config/development.js b/src/config/development.js index 97867457..02736a84 100644 --- a/src/config/development.js +++ b/src/config/development.js @@ -1,9 +1,5 @@ // @flow -import { - TX_SERVICE_HOST, - SIGNATURES_VIA_METAMASK, - RELAY_API_URL, -} from '~/config/names' +import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from '~/config/names' const devConfig = { [TX_SERVICE_HOST]: 'https://safe-transaction.staging.gnosisdev.com/api/v1/', diff --git a/src/config/index.js b/src/config/index.js index 5eebb734..0b7e8db7 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,10 +1,6 @@ // @flow import { ensureOnce } from '~/utils/singleton' -import { - TX_SERVICE_HOST, - SIGNATURES_VIA_METAMASK, - RELAY_API_URL, -} from '~/config/names' +import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from '~/config/names' import devConfig from './development' import testConfig from './testing' import prodConfig from './production' diff --git a/src/config/production.js b/src/config/production.js index 5a0eab77..fd923f3c 100644 --- a/src/config/production.js +++ b/src/config/production.js @@ -1,9 +1,5 @@ // @flow -import { - TX_SERVICE_HOST, - SIGNATURES_VIA_METAMASK, - RELAY_API_URL, -} from '~/config/names' +import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from '~/config/names' const prodConfig = { [TX_SERVICE_HOST]: 'https://safe-transaction.staging.gnosisdev.com/api/v1/', diff --git a/src/config/testing.js b/src/config/testing.js index a43f6f8e..501db9de 100644 --- a/src/config/testing.js +++ b/src/config/testing.js @@ -1,9 +1,5 @@ // @flow -import { - TX_SERVICE_HOST, - SIGNATURES_VIA_METAMASK, - RELAY_API_URL, -} from '~/config/names' +import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from '~/config/names' const testConfig = { [TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/', diff --git a/src/logic/safe/transactions/send.js b/src/logic/safe/transactions/send.js index 6bba674d..0381a7f5 100644 --- a/src/logic/safe/transactions/send.js +++ b/src/logic/safe/transactions/send.js @@ -109,8 +109,8 @@ export const executeTransaction = async ( .encodeABI() const errMsg = await getErrorMessage(safeInstance.address, 0, executeDataUsedSignatures, sender) console.log(`Error executing the TX: ${errMsg}`) - - throw error; + + throw error } } diff --git a/src/logic/tokens/utils/tokenHelpers.js b/src/logic/tokens/utils/tokenHelpers.js index 9d714005..b97b8b6d 100644 --- a/src/logic/tokens/utils/tokenHelpers.js +++ b/src/logic/tokens/utils/tokenHelpers.js @@ -21,7 +21,7 @@ export const getEthAsToken = (balance: string) => { } export const calculateActiveErc20TokensFrom = (tokens: List) => { - const activeTokens = List().withMutations(list => tokens.forEach((token: Token) => { + const activeTokens = List().withMutations((list) => tokens.forEach((token: Token) => { const isDeactivated = isEther(token.symbol) || !token.status if (isDeactivated) { return diff --git a/src/routes/open/components/Layout.stories.js b/src/routes/open/components/Layout.stories.js index 98f4a541..08606dc3 100644 --- a/src/routes/open/components/Layout.stories.js +++ b/src/routes/open/components/Layout.stories.js @@ -8,7 +8,7 @@ import { getProviderInfo } from '~/logic/wallets/getWeb3' import { sleep } from '~/utils/timer' import Component from './Layout' -const FrameDecorator = story =>
{story()}
+const FrameDecorator = (story) =>
{story()}
const store = new Store({ safeAddress: '', diff --git a/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js b/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js index e4697f0e..f32d20c0 100644 --- a/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js +++ b/src/routes/open/components/SafeOwnersConfirmationsForm/validators.js @@ -1,7 +1,5 @@ // @flow -import { - uniqueAddress, -} from '~/components/forms/validator' +import { uniqueAddress } from '~/components/forms/validator' export const getAddressValidator = (addresses: string[], position: number) => { // thanks Rich Harris diff --git a/src/routes/open/components/fields.js b/src/routes/open/components/fields.js index fa280c90..a786ec73 100644 --- a/src/routes/open/components/fields.js +++ b/src/routes/open/components/fields.js @@ -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+Address$/.test(key) && !!values[key]) + .filter((key) => /^owner\d+Address$/.test(key) && !!values[key]) return accounts.length } diff --git a/src/routes/open/utils/safeDataExtractor.js b/src/routes/open/utils/safeDataExtractor.js index 5d198bfc..b20f070f 100644 --- a/src/routes/open/utils/safeDataExtractor.js +++ b/src/routes/open/utils/safeDataExtractor.js @@ -4,17 +4,17 @@ import { makeOwner } from '~/routes/safe/store/models/owner' export const getAccountsFrom = (values: Object): string[] => { const accounts = Object.keys(values) .sort() - .filter(key => /^owner\d+Address$/.test(key)) + .filter((key) => /^owner\d+Address$/.test(key)) - return accounts.map(account => values[account]).slice(0, values.owners) + return accounts.map((account) => values[account]).slice(0, values.owners) } export const getNamesFrom = (values: Object): string[] => { const accounts = Object.keys(values) .sort() - .filter(key => /^owner\d+Name$/.test(key)) + .filter((key) => /^owner\d+Name$/.test(key)) - return accounts.map(account => values[account]).slice(0, values.owners) + return accounts.map((account) => values[account]).slice(0, values.owners) } export const getOwnersFrom = (names: string[], addresses: string[]): Array => { diff --git a/src/routes/opening/Layout.stories.js b/src/routes/opening/Layout.stories.js index 5c208cbb..940a9062 100644 --- a/src/routes/opening/Layout.stories.js +++ b/src/routes/opening/Layout.stories.js @@ -5,7 +5,7 @@ import styles from '~/components/layout/PageFrame/index.scss' import { ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3' import Component from './component' -const FrameDecorator = story =>
{story()}
+const FrameDecorator = (story) =>
{story()}
storiesOf('Routes /opening', module) .addDecorator(FrameDecorator) diff --git a/src/routes/safe/components/Balances/Receive/index.jsx b/src/routes/safe/components/Balances/Receive/index.jsx index 577bad56..34f4cfb2 100644 --- a/src/routes/safe/components/Balances/Receive/index.jsx +++ b/src/routes/safe/components/Balances/Receive/index.jsx @@ -14,13 +14,13 @@ import Row from '~/components/layout/Row' import Hairline from '~/components/layout/Hairline' import Col from '~/components/layout/Col' import { - xxl, lg, sm, md, background, secondary, + lg, md, secondary, secondaryText, } from '~/theme/variables' import { copyToClipboard } from '~/utils/clipboard' const styles = () => ({ heading: { - padding: `${sm} ${lg}`, + padding: `${md} ${lg}`, justifyContent: 'space-between', maxHeight: '75px', boxSizing: 'border-box', @@ -32,27 +32,27 @@ const styles = () => ({ height: '35px', width: '35px', }, - detailsContainer: { - backgroundColor: background, - }, qrContainer: { backgroundColor: '#fff', padding: md, - borderRadius: '3px', - boxShadow: '0 0 5px 0 rgba(74, 85, 121, 0.5)', + borderRadius: '6px', + border: `1px solid ${secondaryText}`, }, safeName: { - margin: `${xxl} 0 20px`, + margin: `${lg} 0 ${lg}`, }, buttonRow: { height: '84px', justifyContent: 'center', - }, - button: { - height: '42px', + '& > button': { + fontFamily: 'Averta', + fontSize: '16px', + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + }, }, addressContainer: { - marginTop: '28px', + marginTop: '25px', + marginBottom: '25px', }, address: { marginLeft: '6px', @@ -84,7 +84,7 @@ const Receive = ({ - + {safeName} @@ -109,7 +109,7 @@ const Receive = ({ - diff --git a/src/routes/safe/components/Balances/SendModal/SafeInfo/index.jsx b/src/routes/safe/components/Balances/SendModal/SafeInfo/index.jsx index 0246c0bf..03084b4d 100644 --- a/src/routes/safe/components/Balances/SendModal/SafeInfo/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/SafeInfo/index.jsx @@ -24,7 +24,7 @@ const styles = () => ({ letterSpacing: -0.5, backgroundColor: border, width: 'fit-content', - padding: '6px', + padding: '5px 10px', marginTop: xs, borderRadius: '3px', }, diff --git a/src/routes/safe/components/Balances/SendModal/index.jsx b/src/routes/safe/components/Balances/SendModal/index.jsx index b20bb25f..2a5d5799 100644 --- a/src/routes/safe/components/Balances/SendModal/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/index.jsx @@ -5,9 +5,18 @@ import cn from 'classnames' import { withStyles } from '@material-ui/core/styles' import { type Token } from '~/logic/tokens/store/model/token' import Modal from '~/components/Modal' -import ChooseTxType from './screens/ChooseTxType' -import SendFunds from './screens/SendFunds' -import ReviewTx from './screens/ReviewTx' + +const ChooseTxType = React.lazy(() => import('./screens/ChooseTxType')) + +const SendFunds = React.lazy(() => import('./screens/SendFunds')) + +const ReviewTx = React.lazy(() => import('./screens/ReviewTx')) + +const SendCustomTx = React.lazy(() => import('./screens/SendCustomTx')) + +const ReviewCustomTx = React.lazy(() => import('./screens/ReviewCustomTx')) + +type ActiveScreen = 'chooseTxType' | 'sendFunds' | 'reviewTx' | 'sendCustomTx' | 'reviewCustomTx' type Props = { onClose: () => void, @@ -20,19 +29,23 @@ type Props = { tokens: List, selectedToken: string, createTransaction: Function, + activeScreenType: ActiveScreen } -type ActiveScreen = 'chooseTxType' | 'sendFunds' | 'reviewTx' type TxStateType = | { token: Token, recipientAddress: string, amount: string, + data: string, } | Object const styles = () => ({ - smallerModalWindow: { + scalableModalWindow: { + height: 'auto', + }, + scalableStaticModalWindow: { height: 'auto', position: 'static', }, @@ -49,23 +62,27 @@ const Send = ({ tokens, selectedToken, createTransaction, + activeScreenType, }: Props) => { - const [activeScreen, setActiveScreen] = useState('sendFunds') + const [activeScreen, setActiveScreen] = useState(activeScreenType || 'chooseTxType') const [tx, setTx] = useState({}) - const smallerModalSize = activeScreen === 'chooseTxType' + + useEffect(() => { + setActiveScreen(activeScreenType || 'chooseTxType') + setTx({}) + }, [isOpen]) + + const scalableModalSize = activeScreen === 'chooseTxType' + const handleTxCreation = (txInfo) => { setActiveScreen('reviewTx') setTx(txInfo) } - const onClickBack = () => setActiveScreen('sendFunds') - useEffect( - () => () => { - setActiveScreen('sendFunds') - setTx({}) - }, - [isOpen], - ) + const handleCustomTxCreation = (customTxInfo) => { + setActiveScreen('reviewCustomTx') + setTx(customTxInfo) + } return ( - + <> {activeScreen === 'chooseTxType' && } {activeScreen === 'sendFunds' && ( )} - + {activeScreen === 'sendCustomTx' && ( + + )} + {activeScreen === 'reviewCustomTx' && ( + + )} + ) } diff --git a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.jsx index 340c89aa..3bb6e459 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.jsx @@ -1,5 +1,6 @@ // @flow import * as React from 'react' +import classNames from 'classnames/bind' import { withStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import IconButton from '@material-ui/core/IconButton' @@ -8,11 +9,14 @@ import Button from '~/components/layout/Button' import Row from '~/components/layout/Row' import Col from '~/components/layout/Col' import Hairline from '~/components/layout/Hairline' -import { lg, sm } from '~/theme/variables' +import Img from '~/components/layout/Img' +import Token from '../assets/token.svg' +import Code from '../assets/code.svg' +import { lg, md, sm } from '~/theme/variables' const styles = () => ({ heading: { - padding: `${sm} ${lg}`, + padding: `${md} ${lg}`, justifyContent: 'space-between', boxSizing: 'border-box', maxHeight: '75px', @@ -26,10 +30,22 @@ const styles = () => ({ }, buttonColumn: { padding: '52px 0', + '& > button': { + fontSize: '16px', + fontFamily: 'Averta', + }, }, - secondButton: { - marginTop: 10, + firstButton: { + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + marginBottom: 15, }, + iconSmall: { + fontSize: 16, + }, + leftIcon: { + marginRight: sm, + }, + }) type Props = { @@ -39,7 +55,7 @@ type Props = { } const ChooseTxType = ({ classes, onClose, setActiveScreen }: Props) => ( - + <> Send @@ -57,22 +73,24 @@ const ChooseTxType = ({ classes, onClose, setActiveScreen }: Props) => ( minHeight={52} onClick={() => setActiveScreen('sendFunds')} variant="contained" + className={classes.firstButton} > - SEND FUNDS + Send funds + Send funds - + ) export default withStyles(styles)(ChooseTxType) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewCustomTx/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewCustomTx/index.jsx new file mode 100644 index 00000000..e391ada9 --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewCustomTx/index.jsx @@ -0,0 +1,159 @@ +// @flow +import React from 'react' +import OpenInNew from '@material-ui/icons/OpenInNew' +import { withStyles } from '@material-ui/core/styles' +import Close from '@material-ui/icons/Close' +import IconButton from '@material-ui/core/IconButton' +import { SharedSnackbarConsumer } from '~/components/SharedSnackBar' +import Paragraph from '~/components/layout/Paragraph' +import Row from '~/components/layout/Row' +import Link from '~/components/layout/Link' +import Col from '~/components/layout/Col' +import Button from '~/components/layout/Button' +import Img from '~/components/layout/Img' +import Block from '~/components/layout/Block' +import Identicon from '~/components/Identicon' +import { copyToClipboard } from '~/utils/clipboard' +import Hairline from '~/components/layout/Hairline' +import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' +import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils' +import { getWeb3 } from '~/logic/wallets/getWeb3' +import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers' +import ArrowDown from '../assets/arrow-down.svg' +import { secondary } from '~/theme/variables' +import { styles } from './style' + +type Props = { + onClose: () => void, + setActiveScreen: Function, + classes: Object, + safeAddress: string, + etherScanLink: string, + safeName: string, + ethBalance: string, + tx: Object, + createTransaction: Function, +} + +const openIconStyle = { + height: '16px', + color: secondary, +} + +const ReviewCustomTx = ({ + onClose, + setActiveScreen, + classes, + safeAddress, + etherScanLink, + safeName, + ethBalance, + tx, + createTransaction, +}: Props) => ( + + {({ openSnackbar }) => { + const submitTx = async () => { + const web3 = getWeb3() + const txRecipient = tx.recipientAddress + const txData = tx.data + const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0 + + createTransaction(safeAddress, txRecipient, txValue, txData, openSnackbar) + onClose() + } + + return ( + <> + + + Send Funds + + 2 of 2 + + + + + + + + + + Arrow Down + + + + + + + + Recipient + + + + + + + + + {tx.recipientAddress} + + + + + + + + + Value + + + + Ether + + {tx.value || 0} + {' ETH'} + + + + + Data (hex encoded) + + + + + + {tx.data} + + + + + + + + + + + ) + }} + +) + +export default withStyles(styles)(ReviewCustomTx) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewCustomTx/style.js b/src/routes/safe/components/Balances/SendModal/screens/ReviewCustomTx/style.js new file mode 100644 index 00000000..75fc858a --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewCustomTx/style.js @@ -0,0 +1,60 @@ +// @flow +import { + lg, md, sm, secondaryText, border, +} from '~/theme/variables' + +export const styles = () => ({ + heading: { + padding: `${md} ${lg}`, + justifyContent: 'flex-start', + boxSizing: 'border-box', + maxHeight: '75px', + }, + annotation: { + letterSpacing: '-1px', + color: secondaryText, + marginRight: 'auto', + marginLeft: '20px', + }, + headingText: { + fontSize: '24px', + }, + closeIcon: { + height: '35px', + width: '35px', + }, + container: { + padding: `${md} ${lg}`, + }, + value: { + marginLeft: sm, + }, + outerData: { + borderRadius: '5px', + border: `1px solid ${border}`, + padding: '11px', + minHeight: '21px', + }, + data: { + wordBreak: 'break-all', + overflow: 'auto', + fontSize: '14px', + fontFamily: 'Averta', + maxHeight: '100px', + letterSpacing: 'normal', + fontStretch: 'normal', + lineHeight: '1.43', + }, + buttonRow: { + height: '84px', + justifyContent: 'center', + '& > button': { + fontFamily: 'Averta', + fontSize: '16px', + }, + }, + submitButton: { + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + marginLeft: '15px', + }, +}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx index 9d233a14..b301038e 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.jsx @@ -27,11 +27,11 @@ import { styles } from './style' type Props = { onClose: () => void, + setActiveScreen: Function, classes: Object, safeAddress: string, etherScanLink: string, safeName: string, - onClickBack: Function, ethBalance: string, tx: Object, createTransaction: Function, @@ -44,13 +44,13 @@ const openIconStyle = { const ReviewTx = ({ onClose, + setActiveScreen, classes, safeAddress, etherScanLink, safeName, ethBalance, tx, - onClickBack, createTransaction, }: Props) => ( @@ -138,20 +138,19 @@ const ReviewTx = ({ - diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/style.js b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/style.js index 94c10bc8..6b44b167 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/style.js +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/style.js @@ -5,7 +5,7 @@ import { export const styles = () => ({ heading: { - padding: `${sm} ${lg}`, + padding: `${md} ${lg}`, justifyContent: 'flex-start', boxSizing: 'border-box', maxHeight: '75px', @@ -32,8 +32,13 @@ export const styles = () => ({ buttonRow: { height: '84px', justifyContent: 'center', - position: 'absolute', - bottom: 0, - width: '100%', + '& > button': { + fontFamily: 'Averta', + fontSize: '16px', + }, + }, + submitButton: { + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + marginLeft: '15px', }, }) diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/index.jsx new file mode 100644 index 00000000..c1954814 --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/index.jsx @@ -0,0 +1,174 @@ +// @flow +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import Close from '@material-ui/icons/Close' +import InputAdornment from '@material-ui/core/InputAdornment' +import IconButton from '@material-ui/core/IconButton' +import Paragraph from '~/components/layout/Paragraph' +import Row from '~/components/layout/Row' +import GnoForm from '~/components/forms/GnoForm' +import AddressInput from '~/components/forms/AddressInput' +import Col from '~/components/layout/Col' +import Button from '~/components/layout/Button' +import Block from '~/components/layout/Block' +import Hairline from '~/components/layout/Hairline' +import ButtonLink from '~/components/layout/ButtonLink' +import Field from '~/components/forms/Field' +import TextField from '~/components/forms/TextField' +import TextareaField from '~/components/forms/TextareaField' +import { + composeValidators, + mustBeFloat, + maxValue, +} from '~/components/forms/validator' +import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo' +import ArrowDown from '../assets/arrow-down.svg' +import { styles } from './style' + +type Props = { + onClose: () => void, + classes: Object, + safeAddress: string, + etherScanLink: string, + safeName: string, + ethBalance: string, + onSubmit: Function, + initialValues: Object, +} + +const SendCustomTx = ({ + classes, + onClose, + safeAddress, + etherScanLink, + safeName, + ethBalance, + onSubmit, + initialValues, +}: Props) => { + const handleSubmit = (values: Object) => { + if (values.data || values.value) { + onSubmit(values) + } + } + + const formMutators = { + setMax: (args, state, utils) => { + utils.changeValue(state, 'value', () => ethBalance) + }, + setRecipient: (args, state, utils) => { + utils.changeValue(state, 'recipientAddress', () => args[0]) + }, + } + + return ( + <> + + + Send custom transactions + + 1 of 2 + + + + + + + {(...args) => { + const mutators = args[3] + + return ( + <> + + + + + Arrow Down + + + + + + + + + + + + + + Value + + + Send max + + + + + + ETH, + }} + /> + + + + + + + + + + + + + + + ) + }} + + + ) +} + +export default withStyles(styles)(SendCustomTx) diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/style.js b/src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/style.js new file mode 100644 index 00000000..96a1149e --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/style.js @@ -0,0 +1,45 @@ +// @flow +import { lg, md } from '~/theme/variables' + +export const styles = () => ({ + heading: { + padding: `${md} ${lg}`, + justifyContent: 'flex-start', + boxSizing: 'border-box', + maxHeight: '75px', + }, + annotation: { + letterSpacing: '-1px', + color: '#a2a8ba', + marginRight: 'auto', + marginLeft: '20px', + }, + manage: { + fontSize: '24px', + }, + closeIcon: { + height: '35px', + width: '35px', + }, + formContainer: { + padding: `${md} ${lg}`, + }, + buttonRow: { + height: '84px', + justifyContent: 'center', + '& > button': { + fontFamily: 'Averta', + fontSize: '16px', + }, + }, + submitButton: { + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + marginLeft: '15px', + }, + dataInput: { + '& TextField-root-294': { + lineHeight: 'auto', + border: 'green', + }, + }, +}) diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.jsx b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.jsx index 890863c9..0d342252 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.jsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.jsx @@ -81,24 +81,28 @@ const SendFunds = ({ - - - - - Arrow Down - - - - - - - {(...args) => { - const formState = args[2] - const mutators = args[3] - const { token } = formState.values - - return ( - <> + + {(...args) => { + const formState = args[2] + const mutators = args[3] + const { token } = formState.values + return ( + <> + + + + + Arrow Down + + + + + - - - - - - - ) - }} - - + + + + + + + + ) + }} + ) } diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/style.js b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/style.js index e1d9ef62..31d3c991 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/style.js +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/style.js @@ -1,11 +1,9 @@ // @flow -import { - lg, md, sm, secondaryText, -} from '~/theme/variables' +import { lg, md, secondaryText } from '~/theme/variables' export const styles = () => ({ heading: { - padding: `${sm} ${lg}`, + padding: `${md} ${lg}`, justifyContent: 'flex-start', boxSizing: 'border-box', maxHeight: '75px', @@ -29,5 +27,13 @@ export const styles = () => ({ buttonRow: { height: '84px', justifyContent: 'center', + '& > button': { + fontFamily: 'Averta', + fontSize: '16px', + }, + }, + submitButton: { + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + marginLeft: '15px', }, }) diff --git a/src/routes/safe/components/Balances/SendModal/screens/assets/code.svg b/src/routes/safe/components/Balances/SendModal/screens/assets/code.svg new file mode 100644 index 00000000..ad056d55 --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/assets/code.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/routes/safe/components/Balances/SendModal/screens/assets/token.svg b/src/routes/safe/components/Balances/SendModal/screens/assets/token.svg new file mode 100644 index 00000000..f2abb8c5 --- /dev/null +++ b/src/routes/safe/components/Balances/SendModal/screens/assets/token.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/index.jsx b/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/index.jsx index 75b13370..dbe9cc5d 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/index.jsx +++ b/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/index.jsx @@ -119,10 +119,10 @@ const AddCustomToken = (props: Props) => { } return ( - + <> {() => ( - + <> Add custom token @@ -189,17 +189,17 @@ const AddCustomToken = (props: Props) => { - - - + )} - + ) } diff --git a/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/style.js b/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/style.js index b09dcabb..3dd3b0ab 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/style.js +++ b/src/routes/safe/components/Balances/Tokens/screens/AddCustomToken/style.js @@ -28,7 +28,4 @@ export const styles = () => ({ height: '84px', justifyContent: 'center', }, - button: { - height: '42px', - }, }) diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx index dd68c257..ad39c8cc 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx +++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.jsx @@ -124,7 +124,7 @@ class Tokens extends React.Component { const filteredTokens = filterBy(filter, tokens) return ( - + <> @@ -179,7 +179,7 @@ class Tokens extends React.Component { ) })} - + ) } } diff --git a/src/routes/safe/components/Balances/index.jsx b/src/routes/safe/components/Balances/index.jsx index 7edf6190..149ccc25 100644 --- a/src/routes/safe/components/Balances/index.jsx +++ b/src/routes/safe/components/Balances/index.jsx @@ -2,12 +2,12 @@ import * as React from 'react' import { List } from 'immutable' import classNames from 'classnames/bind' -import CallMade from '@material-ui/icons/CallMade' -import CallReceived from '@material-ui/icons/CallReceived' import Checkbox from '@material-ui/core/Checkbox' import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' import { withStyles } from '@material-ui/core/styles' +import CallMade from '@material-ui/icons/CallMade' +import CallReceived from '@material-ui/icons/CallReceived' import { type Token } from '~/logic/tokens/store/model/token' import Col from '~/components/layout/Col' import Row from '~/components/layout/Row' @@ -178,7 +178,7 @@ class Balances extends React.Component { onClick={() => this.showSendFunds(row.asset.name)} testId="balance-send-btn" > - + Send )} @@ -189,7 +189,7 @@ class Balances extends React.Component { className={classes.receive} onClick={this.onShow('Receive')} > - + Receive @@ -207,6 +207,7 @@ class Balances extends React.Component { tokens={activeTokens} selectedToken={sendFunds.selectedToken} createTransaction={createTransaction} + activeScreenType="sendFunds" /> ({ root: { @@ -30,17 +30,25 @@ export const styles = (theme: Object) => ({ justifyContent: 'flex-end', visibility: 'hidden', }, - send: { - minWidth: '0px', - marginRight: sm, - width: '70px', - }, receive: { - minWidth: '0px', width: '95px', + minWidth: '95px', + marginLeft: sm, + borderRadius: '4px', + '& > span': { + fontSize: '14px', + }, + }, + send: { + width: '75px', + minWidth: '75px', + borderRadius: '4px', + '& > span': { + fontSize: '14px', + }, }, leftIcon: { - marginRight: xs, + marginRight: sm, }, links: { textDecoration: 'underline', diff --git a/src/routes/safe/components/Layout.jsx b/src/routes/safe/components/Layout.jsx index d7716453..8904b636 100644 --- a/src/routes/safe/components/Layout.jsx +++ b/src/routes/safe/components/Layout.jsx @@ -1,27 +1,35 @@ // @flow import * as React from 'react' +import classNames from 'classnames/bind' import OpenInNew from '@material-ui/icons/OpenInNew' import Tabs from '@material-ui/core/Tabs' import Tab from '@material-ui/core/Tab' +import CallMade from '@material-ui/icons/CallMade' +import CallReceived from '@material-ui/icons/CallReceived' import { withStyles } from '@material-ui/core/styles' import Hairline from '~/components/layout/Hairline' import Block from '~/components/layout/Block' import Identicon from '~/components/Identicon' import Heading from '~/components/layout/Heading' import Row from '~/components/layout/Row' +import Button from '~/components/layout/Button' import Link from '~/components/layout/Link' import Paragraph from '~/components/layout/Paragraph' +import Modal from '~/components/Modal' +import SendModal from './Balances/SendModal' +import Receive from './Balances/Receive' import NoSafe from '~/components/NoSafe' import { type SelectorProps } from '~/routes/safe/container/selector' import { getEtherScanLink } from '~/logic/wallets/getWeb3' import { - sm, xs, secondary, smallFontSize, border, secondaryText, + secondary, border, } from '~/theme/variables' import { copyToClipboard } from '~/utils/clipboard' import { type Actions } from '../container/actions' import Balances from './Balances' import Transactions from './Transactions' import Settings from './Settings' +import { styles } from './style' export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn' export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn' @@ -36,6 +44,12 @@ type Props = SelectorProps & Actions & { classes: Object, granted: boolean, + sendFunds: Object, + showReceive: boolean, + onShow: Function, + onHide: Function, + showSendFunds: Function, + hideSendFunds: Function } const openIconStyle = { @@ -43,40 +57,6 @@ const openIconStyle = { color: secondary, } -const styles = () => ({ - container: { - display: 'flex', - alignItems: 'center', - }, - name: { - marginLeft: sm, - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - user: { - justifyContent: 'left', - }, - open: { - paddingLeft: sm, - width: 'auto', - '&:hover': { - cursor: 'pointer', - }, - }, - readonly: { - fontSize: smallFontSize, - letterSpacing: '0.5px', - color: '#ffffff', - backgroundColor: secondaryText, - textTransform: 'uppercase', - padding: `0 ${sm}`, - marginLeft: sm, - borderRadius: xs, - lineHeight: '28px', - }, -}) - class Layout extends React.Component { constructor(props) { super(props) @@ -112,6 +92,12 @@ class Layout extends React.Component { updateSafe, transactions, userAddress, + sendFunds, + showReceive, + onShow, + onHide, + showSendFunds, + hideSendFunds, } = this.props const { tabIndex } = this.state @@ -142,6 +128,32 @@ class Layout extends React.Component { + + + + + + @@ -190,6 +202,31 @@ class Layout extends React.Component { createTransaction={createTransaction} /> )} + + + + ) } diff --git a/src/routes/safe/components/Layout.stories.js b/src/routes/safe/components/Layout.stories.js index 689a1bfe..bb6ba32d 100644 --- a/src/routes/safe/components/Layout.stories.js +++ b/src/routes/safe/components/Layout.stories.js @@ -5,7 +5,7 @@ import { List } from 'immutable' import styles from '~/components/layout/PageFrame/index.scss' import Component from './Layout' -const FrameDecorator = story =>
{story()}
+const FrameDecorator = (story) =>
{story()}
storiesOf('Routes /safe:address', module) .addDecorator(FrameDecorator) diff --git a/src/routes/safe/components/NoRights/index.stories.js b/src/routes/safe/components/NoRights/index.stories.js index fbd53f8f..03d95d28 100644 --- a/src/routes/safe/components/NoRights/index.stories.js +++ b/src/routes/safe/components/NoRights/index.stories.js @@ -4,7 +4,7 @@ import * as React from 'react' import styles from '~/components/layout/PageFrame/index.scss' import Component from './index.jsx' -const FrameDecorator = story =>
{story()}
+const FrameDecorator = (story) =>
{story()}
storiesOf('Components', module) .addDecorator(FrameDecorator) diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.jsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.jsx index 25217f5b..a952e8d6 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.jsx @@ -44,7 +44,7 @@ const ReviewAddOwner = ({ onSubmit() } return ( - + <> Add new owner @@ -97,7 +97,7 @@ const ReviewAddOwner = ({ - {owners.map(owner => ( + {owners.map((owner) => ( @@ -154,22 +154,22 @@ const ReviewAddOwner = ({ - - + ) } diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.jsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.jsx index b31127d3..0c5af1de 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.jsx @@ -39,7 +39,7 @@ const ThresholdForm = ({ } return ( - + <> Add new owner @@ -52,7 +52,7 @@ const ThresholdForm = ({ {() => ( - + <> @@ -68,8 +68,8 @@ const ThresholdForm = ({ ( - + render={(props) => ( + <> {[...Array(Number(owners.size + 1))].map((x, index) => ( @@ -82,7 +82,7 @@ const ThresholdForm = ({ {props.meta.error} )} - + )} validate={composeValidators(required, mustBeInteger, minValue(1), maxValue(owners.size + 1))} data-testid="threshold-select-input" @@ -101,24 +101,24 @@ owner(s) - - + )} - + ) } diff --git a/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.jsx b/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.jsx index 2eb99b13..6fe47db6 100644 --- a/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.jsx @@ -73,7 +73,7 @@ const EditOwnerComponent = ({ {() => ( - + <> - - - + )}
diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.jsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.jsx index ff2ff84f..06946a08 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/index.jsx @@ -79,13 +79,14 @@ const CheckOwner = ({ - - diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx index 1acc998e..f44cf32a 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx @@ -104,12 +104,11 @@ const ApproveTxModal = ({ -