diff --git a/public/index.html b/public/index.html index f35575de..a2f08f03 100644 --- a/public/index.html +++ b/public/index.html @@ -8,6 +8,6 @@ Gnosis Safe Multisig -
+
diff --git a/src/components/Footer/index.jsx b/src/components/Footer/index.jsx index 8429cf1f..56a2c556 100644 --- a/src/components/Footer/index.jsx +++ b/src/components/Footer/index.jsx @@ -10,8 +10,10 @@ import GnoButtonLink from '~/components/layout/ButtonLink' const useStyles = makeStyles({ footer: { + boxSizing: 'border-box', display: 'flex', flexDirection: 'row', + flexShrink: '1', flexWrap: 'wrap', justifyContent: 'center', margin: '0 auto', @@ -54,11 +56,10 @@ const Footer = () => { return ( diff --git a/src/components/Header/components/CircleDot.jsx b/src/components/Header/components/CircleDot.jsx index 7c54d1e2..4950653a 100644 --- a/src/components/Header/components/CircleDot.jsx +++ b/src/components/Header/components/CircleDot.jsx @@ -4,14 +4,19 @@ import { withStyles } from '@material-ui/core/styles' import Dot from '@material-ui/icons/FiberManualRecord' import Block from '~/components/layout/Block' import Img from '~/components/layout/Img' -import { fancy, border, warning } from '~/theme/variables' +import { + fancy, border, warning, screenSm, +} from '~/theme/variables' const key = require('../assets/key.svg') const triangle = require('../assets/triangle.svg') const styles = () => ({ root: { - display: 'flex', + display: 'none', + [`@media (min-width: ${screenSm}px)`]: { + display: 'flex', + }, }, dot: { position: 'relative', diff --git a/src/components/Header/components/Layout.jsx b/src/components/Header/components/Layout.jsx index ac736753..e94b46b3 100644 --- a/src/components/Header/components/Layout.jsx +++ b/src/components/Header/components/Layout.jsx @@ -13,7 +13,7 @@ import Img from '~/components/layout/Img' import Row from '~/components/layout/Row' import Spacer from '~/components/Spacer' import { - border, sm, md, headerHeight, + border, sm, md, headerHeight, screenSm, } from '~/theme/variables' import Provider from './Provider' import NetworkLabel from './NetworkLabel' @@ -30,26 +30,34 @@ type Props = Open & { const styles = () => ({ root: { backgroundColor: 'white', - padding: 0, - boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)', - minWidth: '280px', borderRadius: sm, + boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)', marginTop: '11px', + minWidth: '280px', + padding: 0, }, summary: { - borderBottom: `solid 2px ${border}`, alignItems: 'center', - height: headerHeight, - boxShadow: '0 2px 4px 0 rgba(212, 212, 211, 0.59)', backgroundColor: 'white', - zIndex: 1301, + borderBottom: `solid 2px ${border}`, + boxShadow: '0 2px 4px 0 rgba(212, 212, 211, 0.59)', + flexWrap: 'nowrap', + height: headerHeight, position: 'fixed', width: '100%', + zIndex: 1301, }, logo: { - padding: `${sm} ${md}`, flexBasis: '95px', - flexGrow: 0, + flexShrink: '0', + flexGrow: '0', + maxWidth: '55px', + padding: sm, + [`@media (min-width: ${screenSm}px)`]: { + maxWidth: 'none', + paddingLeft: md, + paddingRight: md, + }, }, popper: { zIndex: 2000, @@ -65,46 +73,46 @@ const Layout = openHoc( providerInfo, providerDetails, }: Props) => ( - - - - Gnosis Team Safe - - - - - - - - - {(providerRef) => ( - - {({ TransitionProps }) => ( - - <> - - - {providerDetails} - - - - - )} - - )} - - - ), + + + + Gnosis Team Safe + + + + + + + + + {(providerRef) => ( + + {({ TransitionProps }) => ( + + <> + + + {providerDetails} + + + + + )} + + )} + + + ), ) export default withStyles(styles)(Layout) diff --git a/src/components/Header/components/NetworkLabel.jsx b/src/components/Header/components/NetworkLabel.jsx index 152b5efb..f6cd60d0 100644 --- a/src/components/Header/components/NetworkLabel.jsx +++ b/src/components/Header/components/NetworkLabel.jsx @@ -5,7 +5,7 @@ import { getNetwork } from '~/config' import Paragraph from '~/components/layout/Paragraph' import Col from '~/components/layout/Col' import { - xs, sm, md, border, + xs, sm, md, border, screenSm, } from '~/theme/variables' const network = getNetwork() @@ -14,14 +14,22 @@ const formattedNetwork = network[0].toUpperCase() + network.substring(1).toLower const useStyles = makeStyles({ container: { flexGrow: 0, - padding: `0 ${md}`, + padding: `0 ${sm}`, + [`@media (min-width: ${screenSm}px)`]: { + paddingLeft: md, + paddingRight: md, + }, }, text: { background: border, - padding: `${xs} ${sm}`, borderRadius: '3px', - marginLeft: sm, lineHeight: 'normal', + margin: '0', + padding: `${xs} ${sm}`, + + [`@media (min-width: ${screenSm}px)`]: { + marginLeft: '8px', + }, }, }) diff --git a/src/components/Header/components/Provider.jsx b/src/components/Header/components/Provider.jsx index 272283fb..6a54fa68 100644 --- a/src/components/Header/components/Provider.jsx +++ b/src/components/Header/components/Provider.jsx @@ -7,7 +7,7 @@ import ExpandMore from '@material-ui/icons/ExpandMore' import Col from '~/components/layout/Col' import Divider from '~/components/layout/Divider' import { type Open } from '~/components/hoc/OpenHoc' -import { sm, md } from '~/theme/variables' +import { sm, md, screenSm } from '~/theme/variables' type Props = Open & { classes: Object, @@ -18,22 +18,29 @@ type Props = Open & { const styles = () => ({ root: { - height: '100%', - display: 'flex', alignItems: 'center', - flexBasis: '284px', - marginRight: '20px', + display: 'flex', + height: '100%', + + [`@media (min-width: ${screenSm}px)`]: { + flexBasis: '284px', + marginRight: '20px', + }, }, provider: { - padding: `${sm} ${md}`, alignItems: 'center', - flex: '1 1 auto', - display: 'flex', cursor: 'pointer', + display: 'flex', + flex: '1 1 auto', + padding: sm, + [`@media (min-width: ${screenSm}px)`]: { + paddingLeft: md, + paddingRight: md, + }, }, expand: { - width: '30px', height: '30px', + width: '30px', }, }) diff --git a/src/components/Header/components/ProviderInfo/ProviderAccessible.jsx b/src/components/Header/components/ProviderInfo/ProviderAccessible.jsx index d6503524..d9c58280 100644 --- a/src/components/Header/components/ProviderInfo/ProviderAccessible.jsx +++ b/src/components/Header/components/ProviderInfo/ProviderAccessible.jsx @@ -4,7 +4,7 @@ import { withStyles } from '@material-ui/core/styles' import Dot from '@material-ui/icons/FiberManualRecord' import Paragraph from '~/components/layout/Paragraph' import Col from '~/components/layout/Col' -import { connected as connectedBg, sm } from '~/theme/variables' +import { screenSm, connected as connectedBg, sm } from '~/theme/variables' import Identicon from '~/components/Identicon' import { shortVersionOf } from '~/logic/wallets/ethAddresses' import CircleDot from '~/components/Header/components/CircleDot' @@ -21,23 +21,33 @@ const styles = () => ({ network: { fontFamily: 'Averta, sans-serif', }, - logo: { - height: '15px', - width: '15px', - top: '12px', - position: 'relative', - right: '10px', - backgroundColor: '#ffffff', + identicon: { + display: 'none', + [`@media (min-width: ${screenSm}px)`]: { + display: 'block', + }, + }, + dot: { + backgroundColor: '#fff', borderRadius: '15px', color: connectedBg, + display: 'none', + height: '15px', + position: 'relative', + right: '10px', + top: '12px', + width: '15px', + [`@media (min-width: ${screenSm}px)`]: { + display: 'block', + }, }, account: { - paddingRight: sm, + alignItems: 'start', display: 'flex', flexDirection: 'column', - justifyContent: 'left', - alignItems: 'start', flexGrow: 1, + justifyContent: 'left', + paddingRight: sm, }, address: { letterSpacing: '-0.5px', @@ -56,8 +66,8 @@ const ProviderInfo = ({ <> {connected && ( <> - - + + )} {!connected && } diff --git a/src/components/Header/components/ProviderInfo/ProviderDisconnected.jsx b/src/components/Header/components/ProviderInfo/ProviderDisconnected.jsx index ec780896..e6ee9db2 100644 --- a/src/components/Header/components/ProviderInfo/ProviderDisconnected.jsx +++ b/src/components/Header/components/ProviderInfo/ProviderDisconnected.jsx @@ -17,15 +17,16 @@ const styles = () => ({ fontFamily: 'Averta, sans-serif', }, account: { - paddingRight: sm, + alignItems: 'start', display: 'flex', flexDirection: 'column', - justifyContent: 'center', - alignItems: 'start', flexGrow: 1, + justifyContent: 'center', + paddingRight: sm, }, connect: { letterSpacing: '-0.5px', + whiteSpace: 'nowrap', }, }) diff --git a/src/components/Header/components/SafeListHeader/index.jsx b/src/components/Header/components/SafeListHeader/index.jsx index b3a265c4..42d3e65a 100644 --- a/src/components/Header/components/SafeListHeader/index.jsx +++ b/src/components/Header/components/SafeListHeader/index.jsx @@ -8,7 +8,7 @@ import ExpandLessIcon from '@material-ui/icons/ExpandLess' import Paragraph from '~/components/layout/Paragraph' import Col from '~/components/layout/Col' import { - xs, sm, md, border, + xs, sm, md, border, screenSm, } from '~/theme/variables' import { safesCountSelector } from '~/routes/safe/store/selectors' import { SidebarContext } from '~/components/Sidebar' @@ -18,7 +18,11 @@ export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN' const useStyles = makeStyles({ container: { flexGrow: 0, - padding: `0 ${md}`, + padding: `0 ${sm}`, + [`@media (min-width: ${screenSm}px)`]: { + paddingLeft: md, + paddingRight: md, + }, }, counter: { background: border, @@ -60,7 +64,7 @@ const SafeListHeader = ({ safesCount }: Props) => { ) } -export default connect( +export default connect( // $FlowFixMe (state) => ({ safesCount: safesCountSelector(state) }), null, diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx index 032b13be..27d24761 100644 --- a/src/components/Header/index.jsx +++ b/src/components/Header/index.jsx @@ -81,11 +81,11 @@ class HeaderComponent extends React.PureComponent { return ( ) } diff --git a/src/components/Sidebar/SafeList/index.jsx b/src/components/Sidebar/SafeList/index.jsx index 7fa92edf..00a1ec3a 100644 --- a/src/components/Sidebar/SafeList/index.jsx +++ b/src/components/Sidebar/SafeList/index.jsx @@ -57,6 +57,7 @@ const useStyles = makeStyles({ }, safeName: { color: primary, + overflowWrap: 'break-word', }, safeAddress: { color: disabled, diff --git a/src/components/Sidebar/index.jsx b/src/components/Sidebar/index.jsx index 512f306d..b16f67de 100644 --- a/src/components/Sidebar/index.jsx +++ b/src/components/Sidebar/index.jsx @@ -97,32 +97,34 @@ const Sidebar = ({ - - - } - onChange={handleFilterChange} - onCancelSearch={handleFilterCancel} - value={filter} - /> - - + + + + } + value={filter} + /> + + + diff --git a/src/components/Sidebar/style.js b/src/components/Sidebar/style.js index d0ef5b53..c8bd7f11 100644 --- a/src/components/Sidebar/style.js +++ b/src/components/Sidebar/style.js @@ -1,7 +1,7 @@ // @flow import { makeStyles } from '@material-ui/core/styles' import { - xs, mediumFontSize, secondaryText, md, headerHeight, + xs, mediumFontSize, secondaryText, md, headerHeight, screenSm, } from '~/theme/variables' const sidebarWidth = '400px' @@ -12,17 +12,50 @@ const sidebarBorderRadius = '8px' const useSidebarStyles = makeStyles({ sidebar: { - width: sidebarWidth, - marginLeft: sidebarMarginLeft, borderRadius: sidebarBorderRadius, + marginLeft: sidebarMarginLeft, top: sidebarMarginTop, + width: sidebarWidth, }, sidebarPaper: { - width: sidebarWidth, - marginLeft: sidebarMarginLeft, - top: `calc(${headerHeight} + ${sidebarMarginTop})`, - maxHeight: `calc(100vh - ${headerHeight} - ${sidebarMarginTop} - ${sidebarMarginBottom})`, borderRadius: sidebarBorderRadius, + marginLeft: sidebarMarginLeft, + maxHeight: `calc(100vh - ${headerHeight} - ${sidebarMarginTop} - ${sidebarMarginBottom})`, + top: `calc(${headerHeight} + ${sidebarMarginTop})`, + width: sidebarWidth, + maxWidth: `calc(100% - ${sidebarMarginLeft} - ${sidebarMarginLeft})`, + + [`@media (min-width: ${screenSm}px)`]: { + maxWidth: 'none', + }, + }, + topComponents: { + alignItems: 'center', + flexFlow: 'column', + paddingBottom: '30px', + + [`@media (min-width: ${screenSm}px)`]: { + flexFlow: 'row', + paddingBottom: '0', + }, + }, + searchWrapper: { + width: '100%', + [`@media (min-width: ${screenSm}px)`]: { + width: 'auto', + }, + }, + divider: { + display: 'none', + [`@media (min-width: ${screenSm}px)`]: { + display: 'block', + }, + }, + spacer: { + display: 'none', + [`@media (min-width: ${screenSm}px)`]: { + display: 'block', + }, }, headerPlaceholder: { minHeight: headerHeight, @@ -48,14 +81,16 @@ const useSidebarStyles = makeStyles({ }, }, searchContainer: { - width: '190px', + flexGrow: '1', marginLeft: xs, marginRight: xs, + minWidth: '190px', }, searchRoot: { letterSpacing: '-0.5px', border: 'none', boxShadow: 'none', + flexGrow: '1', '& > button': { display: 'none', }, diff --git a/src/components/Spacer/index.jsx b/src/components/Spacer/index.jsx index 114a94f1..8d7f3fb8 100644 --- a/src/components/Spacer/index.jsx +++ b/src/components/Spacer/index.jsx @@ -1,8 +1,12 @@ // @flow import * as React from 'react' +type Props = { + className?: string, +} + const style = { flexGrow: 1, } -export default () =>
+export default ({ className }: Props) =>
diff --git a/src/components/layout/Divider/index.js b/src/components/layout/Divider/index.js index 3dca7fde..29bd1e90 100644 --- a/src/components/layout/Divider/index.js +++ b/src/components/layout/Divider/index.js @@ -2,11 +2,15 @@ import * as React from 'react' import { border } from '~/theme/variables' -const style = { - height: '100%', - borderRight: `solid 2px ${border}`, +type Props = { + className?: string, } -const Divider = () =>
+const style = { + borderRight: `solid 2px ${border}`, + height: '100%', +} + +const Divider = ({ className }: Props) =>
export default Divider diff --git a/src/components/layout/Hairline/index.js b/src/components/layout/Hairline/index.js index 44648fe4..b518d632 100644 --- a/src/components/layout/Hairline/index.js +++ b/src/components/layout/Hairline/index.js @@ -12,16 +12,19 @@ const calculateStyleFrom = (color?: string, margin?: Size) => ({ }) type Props = { - margin?: Size, + className?: string, color?: string, + margin?: Size, style?: Object, } -const Hairline = ({ margin, color, style }: Props) => { +const Hairline = ({ + margin, color, style, className, +}: Props) => { const calculatedStyles = calculateStyleFrom(color, margin) const mergedStyles = { ...calculatedStyles, ...(style || {}) } - return
+ return
} export default Hairline diff --git a/src/logic/notifications/notificationTypes.js b/src/logic/notifications/notificationTypes.js index eb32237f..96009a25 100644 --- a/src/logic/notifications/notificationTypes.js +++ b/src/logic/notifications/notificationTypes.js @@ -69,7 +69,10 @@ export type Notifications = { // AddressBook ADDRESS_BOOK_NEW_ENTRY_SUCCESS: Notification, ADDRESS_BOOK_EDIT_ENTRY_SUCCESS: Notification, - ADDRESS_BOOK_DELETE_ENTRY_SUCCESS: Notification + ADDRESS_BOOK_DELETE_ENTRY_SUCCESS: Notification, + + // Safe Version + SAFE_NEW_VERSION_AVAILABLE: Notification, } export const NOTIFICATIONS: Notifications = { @@ -227,4 +230,10 @@ export const NOTIFICATIONS: Notifications = { message: 'Entry deleted successfully', options: { variant: SUCCESS, persist: false, preventDuplicate: false }, }, + + // Safe Version + SAFE_NEW_VERSION_AVAILABLE: { + message: 'There is a new version available for this Safe', + options: { variant: WARNING, persist: false, preventDuplicate: true }, + }, } diff --git a/src/logic/safe/utils/safeVersion.js b/src/logic/safe/utils/safeVersion.js new file mode 100644 index 00000000..cb6773ea --- /dev/null +++ b/src/logic/safe/utils/safeVersion.js @@ -0,0 +1,22 @@ +// @flow +import semverValid from 'semver/functions/valid' +import semverLessThan from 'semver/functions/lt' +import { getSafeMasterContract } from '~/logic/contracts/safeContracts' +import { getSafeLastVersion } from '~/config' + +export const getSafeVersion = async () => { + let current + let latest + try { + const safeMaster = await getSafeMasterContract() + const safeMasterVersion = await safeMaster.VERSION() + current = semverValid(safeMasterVersion) + latest = semverValid(getSafeLastVersion()) + const needUpdate = latest ? semverLessThan(current, latest) : false + + return { current, latest, needUpdate } + } catch (err) { + console.error(err) + throw err + } +} diff --git a/src/routes/load/components/OwnerList/index.jsx b/src/routes/load/components/OwnerList/index.jsx index 5bb560a5..b8460f4a 100644 --- a/src/routes/load/components/OwnerList/index.jsx +++ b/src/routes/load/components/OwnerList/index.jsx @@ -1,6 +1,7 @@ // @flow import React, { useState, useEffect } from 'react' import { withStyles } from '@material-ui/core/styles' +import TableContainer from '@material-ui/core/TableContainer' import Block from '~/components/layout/Block' import Field from '~/components/forms/Field' import { required } from '~/components/forms/validator' @@ -14,7 +15,7 @@ import Hairline from '~/components/layout/Hairline' import EtherscanBtn from '~/components/EtherscanBtn' import CopyBtn from '~/components/CopyBtn' import { - sm, md, lg, border, disabled, extraSmallFontSize, + sm, md, lg, border, disabled, extraSmallFontSize, screenSm, } from '~/theme/variables' import { getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields' import { FIELD_LOAD_ADDRESS, THRESHOLD } from '~/routes/load/components/fields' @@ -30,8 +31,13 @@ const styles = () => ({ display: 'flex', justifyContent: 'flex-start', }, - ownerNames: { - maxWidth: '400px', + ownerName: { + marginBottom: '15px', + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + marginBottom: '0', + minWidth: '0', + }, }, ownerAddresses: { alignItems: 'center', @@ -118,39 +124,41 @@ const OwnerListComponent = (props: Props) => { - - NAME - ADDRESS - - - - {owners.map((address, index) => ( - - - - - - - - - {address} - - - - - - - ))} - + + + NAME + ADDRESS + + + + {owners.map((address, index) => ( + + + + + + + + + {address} + + + + + + + ))} + + ) } diff --git a/src/routes/load/components/ReviewInformation/index.jsx b/src/routes/load/components/ReviewInformation/index.jsx index 0eb38637..f3677b33 100644 --- a/src/routes/load/components/ReviewInformation/index.jsx +++ b/src/routes/load/components/ReviewInformation/index.jsx @@ -2,6 +2,7 @@ import * as React from 'react' import classNames from 'classnames' import { withStyles } from '@material-ui/core/styles' +import TableContainer from '@material-ui/core/TableContainer' import Block from '~/components/layout/Block' import Identicon from '~/components/Identicon' import OpenPaper from '~/components/Stepper/OpenPaper' @@ -12,7 +13,7 @@ import Paragraph from '~/components/layout/Paragraph' import CopyBtn from '~/components/CopyBtn' import Hairline from '~/components/layout/Hairline' import { - xs, sm, lg, border, + xs, sm, lg, border, screenSm, } from '~/theme/variables' import { shortVersionOf } from '~/logic/wallets/ethAddresses' import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor' @@ -22,7 +23,23 @@ import type { LayoutProps } from '../Layout' const styles = () => ({ root: { + flexDirection: 'column', minHeight: '300px', + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + }, + }, + detailsColumn: { + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '0', + }, + }, + ownersColumn: { + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '0', + }, }, details: { padding: lg, @@ -40,9 +57,10 @@ const styles = () => ({ whiteSpace: 'nowrap', }, owner: { + alignItems: 'center', + minWidth: 'fit-content', padding: sm, paddingLeft: lg, - alignItems: 'center', }, user: { justifyContent: 'left', @@ -101,7 +119,7 @@ class ReviewComponent extends React.PureComponent { return ( <> - + @@ -147,37 +165,39 @@ class ReviewComponent extends React.PureComponent { - - - - {`${getNumOwnersFrom(values)} Safe owners`} - - - - {owners.map((address, index) => ( - - - - - - - - - {values[getOwnerNameBy(index)]} - - - - {address} + + + + + {`${getNumOwnersFrom(values)} Safe owners`} + + + + {owners.map((address, index) => ( + <> + + + + + + + + {values[getOwnerNameBy(index)]} - - + + + {address} + + + + - - - - {index !== owners.length - 1 && } - - ))} + + + {index !== owners.length - 1 && } + + ))} + diff --git a/src/routes/load/container/Load.jsx b/src/routes/load/container/Load.jsx index c02f1da0..17cdca84 100644 --- a/src/routes/load/container/Load.jsx +++ b/src/routes/load/container/Load.jsx @@ -13,6 +13,7 @@ import Layout from '../components/Layout' import { getNamesFrom, getOwnersFrom } from '~/routes/open/utils/safeDataExtractor' import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '../components/fields' import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts' +import { getWeb3 } from '~/logic/wallets/getWeb3' type Props = SelectorProps & Actions @@ -37,8 +38,12 @@ class Load extends React.Component { onLoadSafeSubmit = async (values: Object) => { try { const { addSafe } = this.props + const web3 = getWeb3() const safeName = values[FIELD_LOAD_NAME] - const safeAddress = values[FIELD_LOAD_ADDRESS] + let safeAddress = values[FIELD_LOAD_ADDRESS] + if (safeAddress) { + safeAddress = web3.utils.toChecksumAddress(safeAddress) + } const ownerNames = getNamesFrom(values) const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) diff --git a/src/routes/open/components/ReviewInformation/index.jsx b/src/routes/open/components/ReviewInformation/index.jsx index bef00275..f422b0e0 100644 --- a/src/routes/open/components/ReviewInformation/index.jsx +++ b/src/routes/open/components/ReviewInformation/index.jsx @@ -2,6 +2,7 @@ import * as React from 'react' import classNames from 'classnames' import { withStyles } from '@material-ui/core/styles' +import TableContainer from '@material-ui/core/TableContainer' import { estimateGasForDeployingSafe } from '~/logic/contracts/safeContracts' import { getNamesFrom, getAccountsFrom } from '~/routes/open/utils/safeDataExtractor' import Block from '~/components/layout/Block' @@ -14,7 +15,7 @@ import Row from '~/components/layout/Row' import Paragraph from '~/components/layout/Paragraph' import { formatAmount } from '~/logic/tokens/utils/formatAmount' import { - sm, md, lg, border, background, + sm, md, lg, border, background, screenSm, } from '~/theme/variables' import Hairline from '~/components/layout/Hairline' import { getWeb3 } from '~/logic/wallets/getWeb3' @@ -25,6 +26,21 @@ const { useEffect, useState } = React const styles = () => ({ root: { minHeight: '300px', + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + }, + }, + detailsColumn: { + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '0', + }, + }, + ownersColumn: { + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '0', + }, }, details: { padding: lg, @@ -33,10 +49,10 @@ const styles = () => ({ }, info: { backgroundColor: background, - padding: lg, - justifyContent: 'center', - textAlign: 'center', flexDirection: 'column', + justifyContent: 'center', + padding: lg, + textAlign: 'center', }, owners: { padding: lg, @@ -49,8 +65,10 @@ const styles = () => ({ whiteSpace: 'nowrap', }, owner: { - padding: md, alignItems: 'center', + minWidth: 'fit-content', + padding: sm, + paddingLeft: lg, }, user: { justifyContent: 'left', @@ -102,7 +120,7 @@ const ReviewComponent = ({ values, classes, userAccount }: Props) => { return ( <> - + @@ -127,37 +145,39 @@ const ReviewComponent = ({ values, classes, userAccount }: Props) => { - - - - {`${numOwners} Safe owners`} - - - - {names.map((name, index) => ( - - - - - - - - - {name} - - - - {addresses[index]} + + + + + {`${numOwners} Safe owners`} + + + + {names.map((name, index) => ( + + + + + + + + + {name} - - + + + {addresses[index]} + + + + - - - - - - ))} + + + + + ))} + diff --git a/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx b/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx index 265bcfeb..43d55192 100644 --- a/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx +++ b/src/routes/open/components/SafeOwnersConfirmationsForm/index.jsx @@ -132,7 +132,7 @@ const SafeOwners = (props: Props) => { return ( - + { text="Owner Name" /> - + { Any transaction requires the confirmation of: - - + + { ))} - + out of {' '} diff --git a/src/routes/open/components/SafeOwnersConfirmationsForm/style.js b/src/routes/open/components/SafeOwnersConfirmationsForm/style.js index 8b6eb94b..384509c0 100644 --- a/src/routes/open/components/SafeOwnersConfirmationsForm/style.js +++ b/src/routes/open/components/SafeOwnersConfirmationsForm/style.js @@ -1,6 +1,6 @@ // @flow import { - md, lg, sm, disabled, extraSmallFontSize, + md, lg, sm, disabled, extraSmallFontSize, screenSm, } from '~/theme/variables' export const styles = () => ({ @@ -11,11 +11,32 @@ export const styles = () => ({ padding: `${md} ${lg}`, }, owner: { - padding: `0 ${lg}`, + flexDirection: 'column', marginTop: '12px', + padding: `0 ${lg}`, '&:first-child': { marginTop: 0, }, + + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + }, + }, + ownerName: { + marginBottom: '5px', + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + marginBottom: '0', + minWidth: '0', + }, + }, + ownerAddress: { + marginBottom: '15px', + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + marginBottom: '0', + minWidth: '0', + }, }, header: { padding: `${sm} ${lg}`, @@ -45,4 +66,16 @@ export const styles = () => ({ owners: { paddingLeft: md, }, + ownersAmount: { + flexDirection: 'column', + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + }, + }, + ownersAmountItem: { + minWidth: '100%', + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '0', + }, + }, }) diff --git a/src/routes/safe/components/AddressBook/index.jsx b/src/routes/safe/components/AddressBook/index.jsx index 6fdb1535..92b812b9 100644 --- a/src/routes/safe/components/AddressBook/index.jsx +++ b/src/routes/safe/components/AddressBook/index.jsx @@ -9,6 +9,7 @@ import { withStyles } from '@material-ui/core/styles' import classNames from 'classnames/bind' import CallMade from '@material-ui/icons/CallMade' import { useDispatch, useSelector } from 'react-redux' +import TableContainer from '@material-ui/core/TableContainer' import Block from '~/components/layout/Block' import Row from '~/components/layout/Row' import { type Column, cellWidth } from '~/components/Table/TableHead' @@ -123,97 +124,99 @@ const AddressBookTable = ({ classes }: Props) => { - - {(sortedData: List) => sortedData.map((row: AddressBookEntry, index: number) => { - const userOwner = isUserOwnerOnAnySafe(safesList, row.address) - const hideBorderBottom = index >= 3 + +
+ {(sortedData: List) => sortedData.map((row: AddressBookEntry, index: number) => { + const userOwner = isUserOwnerOnAnySafe(safesList, row.address) + const hideBorderBottom = index >= 3 && index === sortedData.size - 1 && classes.noBorderBottom - return ( - - {autoColumns.map((column: Column) => ( - - {column.id === AB_ADDRESS_ID ? ( - - ) : ( - row[column.id] - )} - - ))} - - - Edit entry { - setSelectedEntry({ entry: { ...row, isOwnerAddress: userOwner } }) - setEditCreateEntryModalOpen(true) - }} - testId={EDIT_ENTRY_BUTTON} - /> - Remove entry { - if (!userOwner) { - setSelectedEntry({ entry: row }) - setDeleteEntryModalOpen(true) - } - }} - testId={REMOVE_ENTRY_BUTTON} - /> - - - - - ) - })} -
+ +
+ + + ) + })} + + setEditCreateEntryModalOpen(false)} diff --git a/src/routes/safe/components/Balances/Receive/index.jsx b/src/routes/safe/components/Balances/Receive/index.jsx index cd31e3e6..e788ac77 100644 --- a/src/routes/safe/components/Balances/Receive/index.jsx +++ b/src/routes/safe/components/Balances/Receive/index.jsx @@ -14,7 +14,7 @@ import Col from '~/components/layout/Col' import EtherscanBtn from '~/components/EtherscanBtn' import CopyBtn from '~/components/CopyBtn' import { - sm, lg, md, secondaryText, + sm, lg, md, secondaryText, screenSm, } from '~/theme/variables' import { copyToClipboard } from '~/utils/clipboard' @@ -53,11 +53,23 @@ const styles = () => ({ }, }, addressContainer: { + flexDirection: 'column', + justifyContent: 'center', margin: `${lg} 0`, + + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + }, }, address: { marginLeft: sm, marginRight: sm, + maxWidth: '70%', + overflowWrap: 'break-word', + + [`@media (min-width: ${screenSm}px)`]: { + maxWidth: 'none', + }, }, }) diff --git a/src/routes/safe/components/Balances/index.jsx b/src/routes/safe/components/Balances/index.jsx index 759e4c40..e5deae7a 100644 --- a/src/routes/safe/components/Balances/index.jsx +++ b/src/routes/safe/components/Balances/index.jsx @@ -4,6 +4,7 @@ import { List } from 'immutable' import classNames from 'classnames/bind' import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' +import TableContainer from '@material-ui/core/TableContainer' import { withStyles } from '@material-ui/core/styles' import CallMade from '@material-ui/icons/CallMade' import CallReceived from '@material-ui/icons/CallReceived' @@ -148,93 +149,95 @@ class Balances extends React.Component {
- - {(sortedData: Array) => sortedData.map((row: any, index: number) => ( - - {autoColumns.map((column: Column) => { - const { id, width, align } = column - let cellItem - switch (id) { - case BALANCE_TABLE_ASSET_ID: { - cellItem = - break + +
+ {(sortedData: Array) => sortedData.map((row: any, index: number) => ( + + {autoColumns.map((column: Column) => { + const { id, width, align } = column + let cellItem + switch (id) { + case BALANCE_TABLE_ASSET_ID: { + cellItem = + break + } + case BALANCE_TABLE_BALANCE_ID: { + cellItem = ( +
+ {row[id]} +
+ ) + break + } + case BALANCE_TABLE_VALUE_ID: { + cellItem =
{row[id]}
+ break + } + default: { + cellItem = null + break + } } - case BALANCE_TABLE_BALANCE_ID: { - cellItem = ( -
- {row[id]} -
- ) - break - } - case BALANCE_TABLE_VALUE_ID: { - cellItem =
{row[id]}
- break - } - default: { - cellItem = null - break - } - } - return ( - - {cellItem} - - ) - })} - - - {granted && ( + return ( + + {cellItem} + + ) + })} + + + {granted && ( + + )} - )} - - - -
- ))} -
+ +
+ + + ))} + + ({ fontSize: 16, }, receiveModal: { - height: '544px', + height: 'auto', + maxWidth: 'calc(100% - 30px)', + minHeight: '544px', + overflow: 'hidden', }, hide: { '&:hover': { diff --git a/src/routes/safe/components/Layout.jsx b/src/routes/safe/components/Layout.jsx index 89427f0d..98f80168 100644 --- a/src/routes/safe/components/Layout.jsx +++ b/src/routes/safe/components/Layout.jsx @@ -133,93 +133,92 @@ const Layout = (props: Props) => { return ( <> - - - - - {name} - - {!granted && Read Only} - - - - {address} - - - + + + + + + {name} + + {!granted && Read Only} + + + + {address} + + + + - + - - - - + + - - - - - - - - + + + + + + { /> )} /> - ( - - )} - /> + } /> { activeScreenType="chooseTxType" /> diff --git a/src/routes/safe/components/Settings/ManageOwners/index.jsx b/src/routes/safe/components/Settings/ManageOwners/index.jsx index 613478a0..a3bef820 100644 --- a/src/routes/safe/components/Settings/ManageOwners/index.jsx +++ b/src/routes/safe/components/Settings/ManageOwners/index.jsx @@ -5,6 +5,7 @@ import { List } from 'immutable' import { withStyles } from '@material-ui/core/styles' import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' +import TableContainer from '@material-ui/core/TableContainer' import Block from '~/components/layout/Block' import Col from '~/components/layout/Col' import Table from '~/components/Table' @@ -145,66 +146,68 @@ class ManageOwners extends React.Component { Add, remove and replace owners or rename existing owners. Owner names are only stored locally and never shared with Gnosis or any third parties. - - {(sortedData: List) => sortedData.map((row: any, index: number) => ( - = 3 && index === sortedData.size - 1 && classes.noBorderBottom)} - data-testid={OWNERS_ROW_TEST_ID} - > - {autoColumns.map((column: Column) => ( - - {column.id === OWNERS_TABLE_ADDRESS_ID ? ( - - ) : ( - row[column.id] - )} - - ))} - - - Edit owner - {granted && ( - <> - Replace owner - {ownerData.size > 1 && ( + +
+ {(sortedData: List) => sortedData.map((row: any, index: number) => ( + = 3 && index === sortedData.size - 1 && classes.noBorderBottom)} + data-testid={OWNERS_ROW_TEST_ID} + > + {autoColumns.map((column: Column) => ( + + {column.id === OWNERS_TABLE_ADDRESS_ID ? ( + + ) : ( + row[column.id] + )} + + ))} + + + Edit owner + {granted && ( + <> Remove owner - )} - - ) } - - - - ))} -
+ {ownerData.size > 1 && ( + Remove owner + )} + + ) } + + + + ))} + +
{granted && ( <> diff --git a/src/routes/safe/components/Settings/RemoveSafeModal/index.jsx b/src/routes/safe/components/Settings/RemoveSafeModal/index.jsx index 188d08d6..b608ba35 100644 --- a/src/routes/safe/components/Settings/RemoveSafeModal/index.jsx +++ b/src/routes/safe/components/Settings/RemoveSafeModal/index.jsx @@ -38,7 +38,7 @@ type Props = Actions & { const RemoveSafeComponent = ({ onClose, isOpen, classes, safeAddress, etherScanLink, safeName, removeSafe, }: Props) => ( - + Remove Safe diff --git a/src/routes/safe/components/Settings/RemoveSafeModal/style.js b/src/routes/safe/components/Settings/RemoveSafeModal/style.js index eab97ef2..6a0f55c5 100644 --- a/src/routes/safe/components/Settings/RemoveSafeModal/style.js +++ b/src/routes/safe/components/Settings/RemoveSafeModal/style.js @@ -5,10 +5,10 @@ import { export const styles = () => ({ heading: { - padding: `${sm} ${lg}`, + boxSizing: 'border-box', justifyContent: 'space-between', maxHeight: '75px', - boxSizing: 'border-box', + padding: `${sm} ${lg}`, }, container: { minHeight: '369px', @@ -54,4 +54,9 @@ export const styles = () => ({ cursor: 'pointer', }, }, + modal: { + height: 'auto', + maxWidth: 'calc(100% - 30px)', + overflow: 'hidden', + }, }) diff --git a/src/routes/safe/components/Settings/SafeDetails/index.jsx b/src/routes/safe/components/Settings/SafeDetails/index.jsx index f9c4090a..70f8c165 100644 --- a/src/routes/safe/components/Settings/SafeDetails/index.jsx +++ b/src/routes/safe/components/Settings/SafeDetails/index.jsx @@ -2,8 +2,6 @@ import React, { useEffect } from 'react' import { makeStyles } from '@material-ui/core/styles' import { withSnackbar } from 'notistack' -import semverLessThan from 'semver/functions/lt' -import semverValid from 'semver/functions/valid' import Block from '~/components/layout/Block' import Col from '~/components/layout/Col' import Field from '~/components/forms/Field' @@ -17,7 +15,7 @@ import Button from '~/components/layout/Button' import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications' import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions' import { styles } from './style' -import { getSafeMasterContract } from '~/logic/contracts/safeContracts' +import { getSafeVersion } from '~/logic/safe/utils/safeVersion' export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input' export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn' @@ -48,18 +46,11 @@ const SafeDetails = (props: Props) => { useEffect(() => { const getVersion = async () => { - let current - let latest try { - const safeMaster = await getSafeMasterContract() - const safeMasterVersion = await safeMaster.VERSION() - current = semverValid(safeMasterVersion) - latest = semverValid(process.env.REACT_APP_LATEST_SAFE_VERSION) - const needUpdate = semverLessThan(current, latest) - + const { current, latest, needUpdate } = await getSafeVersion() setSafeVersions({ current, latest, needUpdate }) } catch (err) { - setSafeVersions({ current: current || 'Version not defined' }) + setSafeVersions({ current: 'Version not defined' }) console.error(err) } } diff --git a/src/routes/safe/components/Settings/SafeDetails/style.js b/src/routes/safe/components/Settings/SafeDetails/style.js index 16e3dfd8..1c52df3b 100644 --- a/src/routes/safe/components/Settings/SafeDetails/style.js +++ b/src/routes/safe/components/Settings/SafeDetails/style.js @@ -12,16 +12,16 @@ export const styles = () => ({ maxWidth: '460px', }, saveBtn: { - marginRight: sm, fontWeight: boldFont, + marginRight: sm, }, controlsRow: { - padding: lg, - position: 'absolute', + borderTop: `2px solid ${border}`, bottom: 0, boxSizing: 'border-box', + padding: lg, + position: 'absolute', width: '100%', - borderTop: `2px solid ${border}`, }, versionNumber: { height: '21px', diff --git a/src/routes/safe/components/Settings/index.jsx b/src/routes/safe/components/Settings/index.jsx index 78770071..49242037 100644 --- a/src/routes/safe/components/Settings/index.jsx +++ b/src/routes/safe/components/Settings/index.jsx @@ -117,7 +117,7 @@ class Settings extends React.Component { /> - + { Safe details - + { {owners.size} - + { Policies - + - + {menuOptionIndex === 1 && ( diff --git a/src/routes/safe/components/Settings/style.js b/src/routes/safe/components/Settings/style.js index ddd51d29..d1cf6ec0 100644 --- a/src/routes/safe/components/Settings/style.js +++ b/src/routes/safe/components/Settings/style.js @@ -1,36 +1,86 @@ // @flow import { - xs, sm, md, border, secondary, bolderFont, background, largeFontSize, fontColor, + xs, sm, md, border, secondary, bolderFont, background, largeFontSize, fontColor, screenSm, } from '~/theme/variables' export const styles = () => ({ root: { backgroundColor: 'white', - boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', - minHeight: '505px', - marginBottom: '54px', - display: 'flex', borderRadius: sm, + boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', + display: 'flex', + flexDirection: 'column', + marginBottom: '54px', + minHeight: '505px', + + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + }, }, settings: { letterSpacing: '-0.5px', }, + menuWrapper: { + display: 'flex', + flexDirection: 'row', + flexGrow: '0', + maxWidth: '100%', + + [`@media (min-width: ${screenSm}px)`]: { + flexDirection: 'row', + maxWidth: 'unset', + }, + }, menu: { - borderRight: `solid 2px ${border}`, + borderBottom: `solid 2px ${border}`, + display: 'flex', + flexDirection: 'row', + flexGrow: '1', height: '100%', + width: '100%', + + [`@media (min-width: ${screenSm}px)`]: { + borderBottom: 'none', + borderRight: `solid 2px ${border}`, + flexDirection: 'column', + width: '250px', + }, }, menuOption: { alignItems: 'center', + borderRight: `solid 1px ${border}`, + boxSizing: 'border-box', cursor: 'pointer', - fontSize: largeFontSize, + flexGrow: '1', + flexShrink: '1', + fontSize: '13px', + justifyContent: 'center', lineHeight: '1.2', - padding: `${md} 0 ${md} ${md}`, + minWidth: '0', + padding: `${md} ${sm}`, + width: '100%', + + [`@media (min-width: ${screenSm}px)`]: { + borderRight: 'none', + flexGrow: '0', + fontSize: largeFontSize, + justifyContent: 'flex-start', + padding: `${md} 0 ${md} ${md}`, + }, + '&:last-of-type': { + borderRight: 'none', + }, '&:first-child': { borderTopLeftRadius: sm, }, '& svg': { display: 'block', - marginRight: sm, + marginRight: xs, + maxWidth: '16px', + + [`@media (min-width: ${screenSm}px)`]: { + marginRight: sm, + }, }, '& .fill': { fill: fontColor, @@ -44,7 +94,18 @@ export const styles = () => ({ fill: secondary, }, }, + contents: { + width: '100%', + }, + hairline: { + display: 'none', + + [`@media (min-width: ${screenSm}px)`]: { + display: 'block', + }, + }, container: { + flexGrow: '1', height: '100%', position: 'relative', }, diff --git a/src/routes/safe/components/Transactions/TxsTable/index.jsx b/src/routes/safe/components/Transactions/TxsTable/index.jsx index 5b2fbde5..799490fe 100644 --- a/src/routes/safe/components/Transactions/TxsTable/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/index.jsx @@ -8,6 +8,7 @@ import ExpandLess from '@material-ui/icons/ExpandLess' import ExpandMore from '@material-ui/icons/ExpandMore' import TableRow from '@material-ui/core/TableRow' import TableCell from '@material-ui/core/TableCell' +import TableContainer from '@material-ui/core/TableContainer' import { withStyles } from '@material-ui/core/styles' import Block from '~/components/layout/Block' import Row from '~/components/layout/Row' @@ -81,77 +82,79 @@ const TxsTable = ({ return ( - - {(sortedData: Array) => sortedData.map((row: any, index: number) => ( - - handleTxExpand(row.tx.safeTxHash)} - data-testid={TRANSACTION_ROW_TEST_ID} - > - {autoColumns.map((column: Column) => ( - - {row[column.id]} + +
+ {(sortedData: Array) => sortedData.map((row: any, index: number) => ( + + handleTxExpand(row.tx.safeTxHash)} + data-testid={TRANSACTION_ROW_TEST_ID} + > + {autoColumns.map((column: Column) => ( + + {row[column.id]} + + ))} + + + + - ))} - - - - - - - {!row.tx.creationTx && ( - - {expandedTx === row.safeTxHash ? : } - - )} - - - {!row.tx.creationTx && ( - - - + + {!row.tx.creationTx && ( + + {expandedTx === row.safeTxHash ? : } + + )} - )} - - ))} -
+ {!row.tx.creationTx && ( + + + + + + )} + + ))} + +
) } diff --git a/src/routes/safe/components/style.js b/src/routes/safe/components/style.js index 934fa0fc..d9e58004 100644 --- a/src/routes/safe/components/style.js +++ b/src/routes/safe/components/style.js @@ -1,12 +1,21 @@ // @flow import { - sm, xs, smallFontSize, secondaryText, secondary, + sm, xs, smallFontSize, secondaryText, secondary, screenSm, } from '~/theme/variables' export const styles = () => ({ container: { - display: 'flex', alignItems: 'center', + display: 'flex', + flexWrap: 'wrap', + }, + userInfo: { + flexWrap: 'nowrap', + marginBottom: sm, + + [`@media (min-width: ${screenSm}px)`]: { + marginBottom: '0', + }, }, name: { marginLeft: sm, @@ -16,12 +25,22 @@ export const styles = () => ({ }, address: { marginRight: sm, + overflow: 'hidden', + maxWidth: '50%', + + [`@media (min-width: ${screenSm}px)`]: { + overflow: 'visible', + maxWidth: 'none', + }, }, user: { justifyContent: 'left', }, receiveModal: { - height: '544px', + height: 'auto', + maxWidth: 'calc(100% - 30px)', + minHeight: '544px', + overflow: 'hidden', }, open: { paddingLeft: sm, @@ -31,39 +50,53 @@ export const styles = () => ({ }, }, readonly: { + backgroundColor: secondaryText, + borderRadius: xs, + color: '#ffffff', fontSize: smallFontSize, letterSpacing: '0.5px', - color: '#ffffff', - backgroundColor: secondaryText, - textTransform: 'uppercase', - padding: `0 ${sm}`, - marginLeft: sm, - borderRadius: xs, lineHeight: '28px', + marginLeft: sm, + padding: `0 ${sm}`, + textTransform: 'uppercase', }, iconSmall: { fontSize: 16, }, balance: { - marginLeft: 'auto', + display: 'flex', overflow: 'hidden', + width: '100%', + + [`@media (min-width: ${screenSm}px)`]: { + marginLeft: 'auto', + width: 'auto', + }, }, receive: { - width: '95px', - minWidth: '95px', - marginLeft: sm, borderRadius: '4px', + marginLeft: sm, + width: '50%', + '& > span': { fontSize: '14px', }, + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '95px', + width: 'auto', + }, }, send: { - width: '75px', - minWidth: '75px', borderRadius: '4px', + width: '50%', + '& > span': { fontSize: '14px', }, + [`@media (min-width: ${screenSm}px)`]: { + minWidth: '75px', + width: 'auto', + }, }, leftIcon: { marginRight: sm, @@ -84,4 +117,9 @@ export const styles = () => ({ fill: secondary, }, }, + nameText: { + overflowWrap: 'break-word', + wordBreak: 'break-word', + whiteSpace: 'normal', + }, }) diff --git a/src/routes/safe/store/actions/createTransaction.js b/src/routes/safe/store/actions/createTransaction.js index 63a2d3e1..195ff76d 100644 --- a/src/routes/safe/store/actions/createTransaction.js +++ b/src/routes/safe/store/actions/createTransaction.js @@ -25,24 +25,31 @@ import { import { getErrorMessage } from '~/test/utils/ethereumErrors' import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses' import { SAFELIST_ADDRESS } from '~/routes/routes' +import type { TransactionProps } from '~/routes/safe/store/models/transaction' -const getLastPendingTxNonce = async (safeAddress: string): Promise => { - let nonce - +const getLastTx = async (safeAddress: string): Promise => { try { const url = buildTxServiceUrl(safeAddress) - const response = await axios.get(url, { params: { limit: 1 } }) - const lastTx = response.data.results[0] - nonce = lastTx.nonce + 1 - } catch (err) { - // use current's safe nonce as fallback - const safeInstance = await getGnosisSafeInstanceAt(safeAddress) - nonce = (await safeInstance.nonce()).toString() + return response.data.results[0] + } catch (e) { + console.error('failed to retrieve last Tx from server', e) + return null } +} - return nonce +const getSafeNonce = async (safeAddress: string): Promise => { + // use current's safe nonce as fallback + const safeInstance = await getGnosisSafeInstanceAt(safeAddress) + return (await safeInstance.nonce()).toString() +} + +const getNewTxNonce = async (txNonce, lastTx, safeAddress) => { + if (!Number.isInteger(Number.parseInt(txNonce, 10))) { + return lastTx === null ? getSafeNonce(safeAddress) : lastTx.nonce + 1 + } + return txNonce } type CreateTransactionArgs = { @@ -78,10 +85,9 @@ const createTransaction = ({ const from = userAccountSelector(state) const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const threshold = await safeInstance.getThreshold() - const nonce = !Number.isInteger(Number.parseInt(txNonce, 10)) - ? await getLastPendingTxNonce(safeAddress) - : txNonce - const isExecution = threshold.toNumber() === 1 || shouldExecute + const lastTx = await getLastTx(safeAddress) + const nonce = await getNewTxNonce(txNonce, lastTx, safeAddress) + const isExecution = (lastTx && lastTx.isExecuted && threshold.toNumber() === 1) || shouldExecute // https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures const sigs = `0x000000000000000000000000${from.replace( diff --git a/src/routes/safe/store/actions/fetchTransactions.js b/src/routes/safe/store/actions/fetchTransactions.js index de87626e..1dc28059 100644 --- a/src/routes/safe/store/actions/fetchTransactions.js +++ b/src/routes/safe/store/actions/fetchTransactions.js @@ -204,10 +204,8 @@ export const buildIncomingTransactionFrom = async (tx: IncomingTxServiceModel) = let symbol = 'ETH' let decimals = 18 - const whenExecutionDate = web3.eth.getBlock(tx.blockNumber) - .then(({ timestamp }) => new Date(timestamp * 1000).toISOString()) - const whenFee = web3.eth.getTransaction(tx.transactionHash).then((t) => bn(t.gas).div(t.gasPrice).toFixed()) - const [executionDate, fee] = await Promise.all([whenExecutionDate, whenFee]) + const fee = await web3.eth.getTransaction(tx.transactionHash) + .then(({ gas, gasPrice }) => bn(gas).div(gasPrice).toFixed()) if (tx.tokenAddress) { try { @@ -217,10 +215,20 @@ export const buildIncomingTransactionFrom = async (tx: IncomingTxServiceModel) = symbol = tokenSymbol decimals = tokenDecimals } catch (err) { - const { methods } = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.tokenAddress) - const [tokenSymbol, tokenDecimals] = await Promise.all([methods.symbol, methods.decimals].map((m) => m().call())) - symbol = web3.utils.toAscii(tokenSymbol) - decimals = tokenDecimals + try { + const { methods } = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.tokenAddress) + const [tokenSymbol, tokenDecimals] = await Promise.all([methods.symbol, methods.decimals].map((m) => m() + .call())) + symbol = web3.utils.hexToString(tokenSymbol) + decimals = tokenDecimals + } catch (e) { + // this is a particular treatment for the DCD token, as it seems to lack of symbol and decimal methods + if (tx.tokenAddress && tx.tokenAddress.toLowerCase() === '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a') { + symbol = 'DCD' + decimals = 9 + } + // if it's not DCD, then we fall to the default values + } } } @@ -231,7 +239,6 @@ export const buildIncomingTransactionFrom = async (tx: IncomingTxServiceModel) = symbol, decimals, fee, - executionDate, executionTxHash: transactionHash, safeTxHash: transactionHash, }) diff --git a/src/routes/safe/store/middleware/notificationsMiddleware.js b/src/routes/safe/store/middleware/notificationsMiddleware.js index 41a2fd1f..77919c5e 100644 --- a/src/routes/safe/store/middleware/notificationsMiddleware.js +++ b/src/routes/safe/store/middleware/notificationsMiddleware.js @@ -14,8 +14,10 @@ import { getIncomingTxAmount } from '~/routes/safe/components/Transactions/TxsTa import updateSafe from '~/routes/safe/store/actions/updateSafe' import { safesMapSelector } from '~/routes/safe/store/selectors' import { isUserOwner } from '~/logic/wallets/ethAddresses' +import { ADD_SAFE } from '~/routes/safe/store/actions/addSafe' +import { getSafeVersion } from '~/logic/safe/utils/safeVersion' -const watchedActions = [ADD_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS] +const watchedActions = [ADD_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS, ADD_SAFE] const notificationsMiddleware = (store: Store) => ( next: Function, @@ -115,6 +117,22 @@ const notificationsMiddleware = (store: Store) => ( }) break } + case ADD_SAFE: { + const { needUpdate } = await getSafeVersion() + const { safe } = action.payload + const notificationKey = `${safe.address}` + if (needUpdate) { + dispatch( + enqueueSnackbar( + enhanceSnackbarForAction( + NOTIFICATIONS.SAFE_NEW_VERSION_AVAILABLE, + notificationKey, + ), + ), + ) + } + break + } default: break }