Merge branch 'development' of github.com:gnosis/safe-react into development

This commit is contained in:
Mati Dastugue 2021-02-17 11:11:53 -03:00
commit 530ba102bf
25 changed files with 238 additions and 163 deletions

View File

@ -1,6 +1,6 @@
{
"name": "safe-react",
"version": "2.19.2",
"version": "3.0.0",
"description": "Allowing crypto users manage funds in a safer way",
"website": "https://github.com/gnosis/safe-react#readme",
"bugs": {

View File

@ -1,7 +1,7 @@
import Checkbox from '@material-ui/core/Checkbox'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import { makeStyles } from '@material-ui/core/styles'
import React, { ReactElement, useEffect, useState } from 'react'
import React, { ReactElement, useEffect, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Button from 'src/components/layout/Button'
import Link from 'src/components/layout/Link'
@ -96,7 +96,7 @@ interface CookiesBannerFormProps {
const CookiesBanner = (): ReactElement => {
const classes = useStyles()
const dispatch = useDispatch()
const dispatch = useRef(useDispatch())
const [showAnalytics, setShowAnalytics] = useState(false)
const [showIntercom, setShowIntercom] = useState(false)
@ -110,7 +110,7 @@ const CookiesBanner = (): ReactElement => {
async function fetchCookiesFromStorage() {
const cookiesState = await loadFromCookie(COOKIES_KEY)
if (!cookiesState) {
dispatch(openCookieBanner({ cookieBannerOpen: true }))
dispatch.current(openCookieBanner({ cookieBannerOpen: true }))
} else {
const { acceptedIntercom, acceptedAnalytics, acceptedNecessary } = cookiesState
if (acceptedIntercom === undefined) {
@ -143,7 +143,7 @@ const CookiesBanner = (): ReactElement => {
await saveCookie(COOKIES_KEY, newState, 365)
setShowAnalytics(!isDesktop)
setShowIntercom(true)
dispatch(openCookieBanner({ cookieBannerOpen: false }))
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
}
const closeCookiesBannerHandler = async () => {
@ -159,7 +159,7 @@ const CookiesBanner = (): ReactElement => {
if (!localIntercom && isIntercomLoaded()) {
closeIntercom()
}
dispatch(openCookieBanner({ cookieBannerOpen: false }))
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
}
if (showAnalytics && !isDesktop) {
@ -254,7 +254,7 @@ const CookiesBanner = (): ReactElement => {
<img
className={classes.intercomImage}
src={IntercomIcon}
onClick={() => dispatch(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))}
onClick={() => dispatch.current(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))}
/>
)}
{!isDesktop && showBanner?.cookieBannerOpen && (

View File

@ -22,7 +22,7 @@ import {
import { UPDATE_TRANSACTION_DETAILS } from 'src/logic/safe/store/actions/fetchTransactionDetails'
import { AppReduxState } from 'src/store'
import { getUTCStartOfDate } from 'src/utils/date'
import { getLocalStartOfDate } from 'src/utils/date'
import { sameString } from 'src/utils/strings'
import { sortObject } from 'src/utils/objects'
@ -78,7 +78,7 @@ export const gatewayTransactions = handleActions<AppReduxState['gatewayTransacti
}
if (isTransactionSummary(value)) {
const startOfDate = getUTCStartOfDate(value.transaction.timestamp)
const startOfDate = getLocalStartOfDate(value.transaction.timestamp)
if (typeof history[startOfDate] === 'undefined') {
history[startOfDate] = []
@ -326,6 +326,11 @@ export const gatewayTransactions = handleActions<AppReduxState['gatewayTransacti
}
case 'queued.next': {
queued.next[nonce] = queued.next[nonce].map((txToUpdate) => {
// prevent setting `PENDING_FAILED` status, if previous status wasn't `PENDING`
if (txStatus === 'PENDING_FAILED' && txToUpdate.txStatus !== 'PENDING') {
return txToUpdate
}
if (typeof id !== 'undefined') {
if (sameString(txToUpdate.id, id)) {
txToUpdate.txStatus = txStatus
@ -339,6 +344,11 @@ export const gatewayTransactions = handleActions<AppReduxState['gatewayTransacti
}
case 'queued.queued': {
queued.queued[nonce] = queued.queued[nonce].map((txToUpdate) => {
// prevent setting `PENDING_FAILED` status, if previous status wasn't `PENDING`
if (txStatus === 'PENDING_FAILED' && txToUpdate.txStatus !== 'PENDING') {
return txToUpdate
}
if (typeof id !== 'undefined') {
if (sameString(txToUpdate.id, id)) {
txToUpdate.txStatus = txStatus

View File

@ -20,6 +20,7 @@ import { createTransaction } from 'src/logic/safe/store/actions/createTransactio
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES, CALL } from 'src/logic/safe/transactions'
import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend'
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
import GasEstimationInfo from './GasEstimationInfo'
import { getNetworkInfo } from 'src/config'
@ -105,6 +106,10 @@ type OwnProps = {
const { nativeCoin } = getNetworkInfo()
const parseTxValue = (value: string | number): string => {
return web3ReadOnly.utils.toBN(value).toString()
}
export const ConfirmTransactionModal = ({
isOpen,
app,
@ -122,7 +127,10 @@ export const ConfirmTransactionModal = ({
const txRecipient: string | undefined = useMemo(() => (txs.length > 1 ? MULTI_SEND_ADDRESS : txs[0]?.to), [txs])
const txData: string | undefined = useMemo(() => (txs.length > 1 ? encodeMultiSendCall(txs) : txs[0]?.data), [txs])
const txValue: string | undefined = useMemo(() => (txs.length > 1 ? '0' : txs[0]?.value), [txs])
const txValue: string | undefined = useMemo(
() => (txs.length > 1 ? '0' : txs[0]?.value && parseTxValue(txs[0]?.value)),
[txs],
)
const operation = useMemo(() => (txs.length > 1 ? DELEGATE_CALL : CALL), [txs])
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()

View File

@ -86,13 +86,13 @@ export const RemoveModuleModal = ({ onClose, selectedModulePair }: RemoveModuleM
const removeSelectedModule = async (txParameters: TxParameters): Promise<void> => {
try {
dispatch(
await dispatch(
createTransaction({
safeAddress,
to: safeAddress,
valueInWei: '0',
txData,
txNonce: txParameters.ethNonce,
txNonce: txParameters.safeNonce,
safeTxGas: txParameters.safeTxGas ? Number(txParameters.safeTxGas) : undefined,
ethParameters: txParameters,
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,

View File

@ -3,6 +3,7 @@ import MenuItem from '@material-ui/core/MenuItem'
import { makeStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import React, { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { List } from 'immutable'
import Field from 'src/components/forms/Field'
@ -20,6 +21,8 @@ import { SafeOwner } from 'src/logic/safe/store/models/safe'
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
import { TransactionFees } from 'src/components/TransactionsFees'
import { TxParametersDetail } from 'src/routes/safe/components/Transactions/helpers/TxParametersDetail'
import { createTransaction } from 'src/logic/safe/store/actions/createTransaction'
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
import { styles } from './style'
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
@ -30,7 +33,6 @@ const THRESHOLD_FIELD_NAME = 'threshold'
const useStyles = makeStyles(styles)
type ChangeThresholdModalProps = {
onChangeThreshold: (newThreshold: number) => void
onClose: () => void
owners?: List<SafeOwner>
safeAddress: string
@ -38,13 +40,13 @@ type ChangeThresholdModalProps = {
}
export const ChangeThresholdModal = ({
onChangeThreshold,
onClose,
owners,
safeAddress,
threshold = 1,
}: ChangeThresholdModalProps): React.ReactElement => {
const classes = useStyles()
const dispatch = useDispatch()
const [data, setData] = useState('')
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
@ -83,11 +85,21 @@ export const ChangeThresholdModal = ({
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
const handleSubmit = (values) => {
const newThreshold = values[THRESHOLD_FIELD_NAME]
const handleSubmit = async ({ txParameters }) => {
await dispatch(
createTransaction({
safeAddress,
to: safeAddress,
valueInWei: '0',
txData: data,
txNonce: txParameters.safeNonce,
safeTxGas: txParameters.safeTxGas ? Number(txParameters.safeTxGas) : undefined,
ethParameters: txParameters,
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
}),
)
onClose()
onChangeThreshold(newThreshold)
}
const closeEditModalCallback = (txParameters: TxParameters) => {
@ -123,7 +135,7 @@ export const ChangeThresholdModal = ({
</IconButton>
</Row>
<Hairline />
<GnoForm initialValues={{ threshold: threshold.toString() }} onSubmit={handleSubmit}>
<GnoForm initialValues={{ threshold: threshold.toString(), txParameters }} onSubmit={handleSubmit}>
{() => (
<>
<Block className={classes.modalContent}>

View File

@ -1,6 +1,6 @@
import { makeStyles } from '@material-ui/core/styles'
import React, { useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useSelector } from 'react-redux'
import Modal from 'src/components/Modal'
import Block from 'src/components/layout/Block'
@ -9,18 +9,13 @@ import Button from 'src/components/layout/Button'
import Heading from 'src/components/layout/Heading'
import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row'
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
import { grantedSelector } from 'src/routes/safe/container/selector'
import { createTransaction } from 'src/logic/safe/store/actions/createTransaction'
import {
safeOwnersSelector,
safeParamAddressFromStateSelector,
safeThresholdSelector,
} from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { useTransactionParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
import { EditTxParametersForm } from 'src/routes/safe/components/Transactions/helpers/EditTxParametersForm'
import { ChangeThresholdModal } from './ChangeThreshold'
import { styles } from './style'
@ -30,46 +25,21 @@ const useStyles = makeStyles(styles)
const ThresholdSettings = (): React.ReactElement => {
const classes = useStyles()
const [isModalOpen, setModalOpen] = useState(false)
const dispatch = useDispatch()
const threshold = useSelector(safeThresholdSelector) || 1
const safeAddress = useSelector(safeParamAddressFromStateSelector)
const owners = useSelector(safeOwnersSelector)
const granted = useSelector(grantedSelector)
const txParameters = useTransactionParameters()
const [activeScreen, setActiveScreen] = useState('form')
const toggleModal = () => {
setModalOpen((prevOpen) => !prevOpen)
}
const onChangeThreshold = async (newThreshold: number) => {
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const txData = safeInstance.methods.changeThreshold(newThreshold).encodeABI()
dispatch(
createTransaction({
safeAddress,
to: safeAddress,
valueInWei: '0',
txData,
txNonce: txParameters.safeNonce,
safeTxGas: txParameters.safeTxGas ? Number(txParameters.safeTxGas) : undefined,
ethParameters: txParameters,
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
}),
)
}
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
}, [trackEvent])
const closeEditTxParameters = () => setActiveScreen('form')
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
return (
<>
<Block className={classes.container}>
@ -98,22 +68,7 @@ const ThresholdSettings = (): React.ReactElement => {
open={isModalOpen}
title="Change Required Confirmations"
>
{activeScreen === 'form' && (
<ChangeThresholdModal
onChangeThreshold={onChangeThreshold}
onClose={toggleModal}
owners={owners}
safeAddress={safeAddress}
threshold={threshold}
/>
)}
{activeScreen === 'editTxParameters' && (
<EditTxParametersForm
txParameters={txParameters}
onClose={closeEditTxParameters}
parametersStatus={getParametersStatus()}
/>
)}
<ChangeThresholdModal onClose={toggleModal} owners={owners} safeAddress={safeAddress} threshold={threshold} />
</Modal>
</>
)

View File

@ -1,5 +1,4 @@
import { Loader } from '@gnosis.pm/safe-react-components'
import { format } from 'date-fns'
import React, { ReactElement } from 'react'
import { InfiniteScroll, SCROLLABLE_TARGET_ID } from 'src/components/InfiniteScroll'
@ -13,6 +12,7 @@ import {
} from './styled'
import { TxHistoryRow } from './TxHistoryRow'
import { TxLocationContext } from './TxLocationProvider'
import { formatWithSchema } from 'src/utils/date'
export const HistoryTxList = (): ReactElement => {
const { count, hasMore, next, transactions } = usePagedHistoryTransactions()
@ -31,7 +31,7 @@ export const HistoryTxList = (): ReactElement => {
<InfiniteScroll dataLength={transactions.length} next={next} hasMore={hasMore}>
{transactions?.map(([timestamp, txs]) => (
<StyledTransactionsGroup key={timestamp}>
<SubTitle size="lg">{format(Number(timestamp), 'MMM d, yyyy')}</SubTitle>
<SubTitle size="lg">{formatWithSchema(Number(timestamp), 'MMM d, yyyy')}</SubTitle>
<StyledTransactions>
{txs.map((transaction) => (
<TxHistoryRow key={transaction.id} transaction={transaction} />

View File

@ -4,18 +4,8 @@ import styled from 'styled-components'
import { DataDecoded } from 'src/logic/safe/store/models/types/gateway.d'
import { isArrayParameter } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils'
import {
DeleteSpendingLimitDetails,
isDeleteAllowance,
isSetAllowance,
ModifySpendingLimitDetails,
} from 'src/routes/safe/components/Transactions/GatewayTransactions/SpendingLimitDetails'
import Value from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value'
const TxDetailsMethodName = styled(Text)`
text-indent: 4px;
`
const TxDetailsMethodParam = styled.div<{ isArrayParameter: boolean }>`
padding-left: 24px;
display: ${({ isArrayParameter }) => (isArrayParameter ? 'block' : 'flex')};
@ -27,7 +17,7 @@ const TxDetailsMethodParam = styled.div<{ isArrayParameter: boolean }>`
`
const TxInfo = styled.div`
padding: 8px 8px 8px 16px;
padding: 8px 0;
`
const StyledMethodName = styled(Text)`
@ -35,21 +25,11 @@ const StyledMethodName = styled(Text)`
`
export const MethodDetails = ({ data }: { data: DataDecoded }): React.ReactElement => {
// FixMe: this way won't scale well
if (isSetAllowance(data.method)) {
return <ModifySpendingLimitDetails data={data} />
}
// FixMe: this way won't scale well
if (isDeleteAllowance(data.method)) {
return <DeleteSpendingLimitDetails data={data} />
}
return (
<TxInfo>
<TxDetailsMethodName size="xl" strong>
<Text size="xl" strong>
{data.method}
</TxDetailsMethodName>
</Text>
{data.parameters?.map((param, index) => (
<TxDetailsMethodParam key={`${data.method}_param-${index}`} isArrayParameter={isArrayParameter(param.type)}>

View File

@ -60,21 +60,21 @@ export const MultiSendDetails = ({ txData }: { txData: TransactionData }): React
const title = `Send ${amount} ${nativeCoin.name} to:`
if (dataDecoded) {
// Backend decoded data
details = <MethodDetails data={dataDecoded} />
} else {
// We couldn't decode it but we have data
details = data && <HexEncodedData hexData={data} />
}
return (
details && (
<MultiSendTxGroup
key={`${data ?? to}-${index}`}
actionTitle={actionTitle}
txDetails={{ title, address: to, dataDecoded }}
>
{details}
</MultiSendTxGroup>
)
<MultiSendTxGroup
key={`${data ?? to}-${index}`}
actionTitle={actionTitle}
txDetails={{ title, address: to, dataDecoded }}
>
{details}
</MultiSendTxGroup>
)
})}
</>

View File

@ -11,7 +11,7 @@ export const OwnerRow = ({ ownerAddress }: { ownerAddress: string }): ReactEleme
return (
<EthHashInfo
hash={ownerAddress}
name={ownerName}
name={ownerName === 'UNKNOWN' ? '' : ownerName}
showIdenticon
showCopyBtn
explorerUrl={getExplorerInfo(ownerAddress)}

View File

@ -3,7 +3,8 @@ import { ThemeColors } from '@gnosis.pm/safe-react-components/dist/theme'
import { Tooltip } from '@material-ui/core'
import CircularProgress from '@material-ui/core/CircularProgress'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import React, { ReactElement, useRef } from 'react'
import React, { ReactElement, useContext, useRef } from 'react'
import styled from 'styled-components'
import CustomIconText from 'src/components/CustomIconText'
import {
@ -13,15 +14,16 @@ import {
Transaction,
} from 'src/logic/safe/store/models/types/gateway.d'
import { TxCollapsedActions } from 'src/routes/safe/components/Transactions/GatewayTransactions/TxCollapsedActions'
import { formatDateTime, formatTime } from 'src/routes/safe/components/Transactions/GatewayTransactions/utils'
import { formatDateTime, formatTime, formatTimeInWords } from 'src/utils/date'
import { KNOWN_MODULES } from 'src/utils/constants'
import styled from 'styled-components'
import { sameString } from 'src/utils/strings'
import { AssetInfo, isTokenTransferAsset } from './hooks/useAssetInfo'
import { TransactionActions } from './hooks/useTransactionActions'
import { TransactionStatusProps } from './hooks/useTransactionStatus'
import { TxTypeProps } from './hooks/useTransactionType'
import { StyledGroupedTransactions, StyledTransaction } from './styled'
import { TokenTransferAmount } from './TokenTransferAmount'
import { TxLocationContext } from './TxLocationProvider'
import { CalculatedVotes } from './TxQueueCollapsed'
const TxInfo = ({ info }: { info: AssetInfo }) => {
@ -126,6 +128,8 @@ export const TxCollapsed = ({
actions,
status,
}: TxCollapsedProps): ReactElement => {
const { txLocation } = useContext(TxLocationContext)
const willBeReplaced = transaction?.txStatus === 'WILL_BE_REPLACED' ? ' will-be-replaced' : ''
const txCollapsedNonce = (
@ -149,7 +153,7 @@ export const TxCollapsed = ({
<div className={'tx-time' + willBeReplaced}>
<Tooltip classes={tooltipStyles} title={formatDateTime(time)} arrow>
<TooltipContent ref={timestamp}>
<Text size="xl">{formatTime(time)}</Text>
<Text size="xl">{txLocation === 'history' ? formatTime(time) : formatTimeInWords(time)}</Text>
</TooltipContent>
</Tooltip>
</div>
@ -203,7 +207,7 @@ export const TxCollapsed = ({
{txCollapsedStatus}
</StyledGroupedTransactions>
) : (
<StyledTransaction>
<StyledTransaction className={sameString(status.text, 'Failed') ? 'failed-transaction' : ''}>
{txCollapsedNonce}
{txCollapsedType}
{txCollapsedInfo}

View File

@ -1,11 +1,26 @@
import { Icon } from '@gnosis.pm/safe-react-components'
import { Icon, theme } from '@gnosis.pm/safe-react-components'
import { Tooltip as TooltipMui } from '@material-ui/core'
import { default as MuiIconButton } from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
import React, { ReactElement } from 'react'
import styled from 'styled-components'
import { Transaction } from 'src/logic/safe/store/models/types/gateway.d'
import { useActionButtonsHandlers } from './hooks/useActionButtonsHandlers'
const Tooltip = withStyles(() => ({
popper: {
zIndex: 2001,
},
tooltip: {
marginBottom: '4px',
backgroundColor: theme.colors.overlay.color,
border: `1px solid ${theme.colors.icon}`,
boxShadow: `1px 2px 4px ${theme.colors.shadow.color}14`,
color: theme.colors.text,
},
}))(TooltipMui)
const IconButton = styled(MuiIconButton)`
padding: 8px !important;
@ -32,26 +47,25 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
return (
<>
{
<IconButton
size="small"
type="button"
onClick={handleConfirmButtonClick}
disabled={disabledActions}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
>
<Icon
type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'}
color="primary"
size="sm"
tooltip={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'}
/>
</IconButton>
<Tooltip title={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'} placement="top">
<IconButton
size="small"
type="button"
onClick={handleConfirmButtonClick}
disabled={disabledActions}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
>
<Icon type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'} color="primary" size="sm" />
</IconButton>
</Tooltip>
}
{canCancel && (
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
<Icon type="circleCross" color="error" size="sm" tooltip="Cancel" />
</IconButton>
<Tooltip title="Cancel" placement="top">
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
<Icon type="circleCross" color="error" size="sm" />
</IconButton>
</Tooltip>
)}
</>
)

View File

@ -1,11 +1,37 @@
import React, { ReactElement } from 'react'
import React, { ReactElement, ReactNode } from 'react'
import { ExpandedTxDetails } from 'src/logic/safe/store/models/types/gateway.d'
import { getNetworkInfo } from 'src/config'
import { ExpandedTxDetails, TransactionData } from 'src/logic/safe/store/models/types/gateway.d'
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
import {
DeleteSpendingLimitDetails,
isDeleteAllowance,
isSetAllowance,
ModifySpendingLimitDetails,
} from './SpendingLimitDetails'
import { TxInfoDetails } from './TxInfoDetails'
import { sameString } from 'src/utils/strings'
import { HexEncodedData } from './HexEncodedData'
import { MethodDetails } from './MethodDetails'
import { MultiSendDetails } from './MultiSendDetails'
const { nativeCoin } = getNetworkInfo()
type DetailsWithTxInfoProps = {
children: ReactNode
txData: TransactionData
}
const DetailsWithTxInfo = ({ children, txData }: DetailsWithTxInfoProps): ReactElement => (
<>
<TxInfoDetails
address={txData.to}
title={`Send ${txData.value ? fromTokenUnit(txData.value, nativeCoin.decimals) : 'n/a'} ${nativeCoin.symbol} to:`}
/>
{children}
</>
)
type TxDataProps = {
txData: ExpandedTxDetails['txData']
}
@ -24,7 +50,11 @@ export const TxData = ({ txData }: TxDataProps): ReactElement | null => {
}
// we render the hex encoded data
return <HexEncodedData hexData={txData.hexData} />
return (
<DetailsWithTxInfo txData={txData}>
<HexEncodedData hexData={txData.hexData} />
</DetailsWithTxInfo>
)
}
// known data and particularly `multiSend` data type
@ -32,6 +62,20 @@ export const TxData = ({ txData }: TxDataProps): ReactElement | null => {
return <MultiSendDetails txData={txData} />
}
// FixMe: this way won't scale well
if (isSetAllowance(txData.dataDecoded.method)) {
return <ModifySpendingLimitDetails data={txData.dataDecoded} />
}
// FixMe: this way won't scale well
if (isDeleteAllowance(txData.dataDecoded.method)) {
return <DeleteSpendingLimitDetails data={txData.dataDecoded} />
}
// we render the decoded data
return <MethodDetails data={txData.dataDecoded} />
return (
<DetailsWithTxInfo txData={txData}>
<MethodDetails data={txData.dataDecoded} />
</DetailsWithTxInfo>
)
}

View File

@ -2,8 +2,9 @@ import { Text } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react'
import { getExplorerInfo } from 'src/config'
import { formatDateTime } from 'src/utils/date'
import { Creation, Transaction } from 'src/logic/safe/store/models/types/gateway.d'
import { formatDateTime, NOT_AVAILABLE } from './utils'
import { NOT_AVAILABLE } from './utils'
import { InlineEthHashInfo, TxDetailsContainer } from './styled'
export const TxInfoCreation = ({ transaction }: { transaction: Transaction }): ReactElement | null => {

View File

@ -30,7 +30,15 @@ export const TxInfoDetails = ({ title, address, isTransferType, txInfo }: TxInfo
const knownAddress = recipientName !== 'UNKNOWN'
const { txLocation } = useContext<TxLocationProps>(TxLocationContext)
const canRepeatTransaction = isTransferType && txLocation === 'history' && txInfo?.direction === 'OUTGOING'
const canRepeatTransaction =
// is transfer type by context
isTransferType &&
// not a Collectible
txInfo?.transferInfo.type !== 'ERC721' &&
// in history list
txLocation === 'history' &&
// it's outgoing
txInfo?.direction === 'OUTGOING'
const [sendModalOpen, setSendModalOpen] = useState(false)
const sendModalOpenHandler = () => {

View File

@ -2,9 +2,10 @@ import { Text } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react'
import { getExplorerInfo } from 'src/config'
import { formatDateTime } from 'src/utils/date'
import { ExpandedTxDetails, isMultiSigExecutionDetails, Operation } from 'src/logic/safe/store/models/types/gateway.d'
import { InlineEthHashInfo } from './styled'
import { formatDateTime, NOT_AVAILABLE } from './utils'
import { NOT_AVAILABLE } from './utils'
export const TxSummary = ({ txDetails }: { txDetails: ExpandedTxDetails }): ReactElement => {
const { txHash, detailedExecutionInfo, executedAt, txData } = txDetails

View File

@ -62,7 +62,7 @@ export const useActionButtonsHandlers = (transaction: Transaction): ActionButton
const isPending = useMemo(() => !!transaction.txStatus.match(/^PENDING.*/), [transaction.txStatus])
const signaturePending = useCallback(addressInList(transaction.executionInfo?.missingSigners), [])
const signaturePending = addressInList(transaction.executionInfo?.missingSigners)
const disabledActions = useMemo(
() =>

View File

@ -380,16 +380,20 @@ export const ApproveTxModal = ({
/>
)}
</Row>
<TransactionFees
gasCostFormatted={gasCostFormatted}
isExecution={isExecution}
isCreation={isCreation}
isOffChainSignature={isOffChainSignature}
txEstimationExecutionStatus={txEstimationExecutionStatus}
/>
</Block>
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
<Block className={classes.gasCostsContainer}>
<TransactionFees
gasCostFormatted={gasCostFormatted}
isExecution={isExecution}
isCreation={isCreation}
isOffChainSignature={isOffChainSignature}
txEstimationExecutionStatus={txEstimationExecutionStatus}
/>
</Block>
)}
{/* Footer */}
<Row align="center" className={classes.buttonRow}>
<Button minHeight={42} minWidth={140} onClick={onClose}>

View File

@ -117,12 +117,14 @@ export const RejectTxModal = ({ isOpen, onClose, gwTransaction }: Props): React.
<TxParametersDetail
txParameters={txParameters}
onEdit={toggleEditMode}
compact={false}
parametersStatus={getParametersStatus()}
isTransactionCreation={isCreation}
isTransactionExecution={isExecution}
/>
<Row>
</Block>
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
<Block className={classes.gasCostsContainer}>
<TransactionFees
gasCostFormatted={gasCostFormatted}
isExecution={isExecution}
@ -130,8 +132,8 @@ export const RejectTxModal = ({ isOpen, onClose, gwTransaction }: Props): React.
isOffChainSignature={isOffChainSignature}
txEstimationExecutionStatus={txEstimationExecutionStatus}
/>
</Row>
</Block>
</Block>
)}
<Row align="center" className={classes.buttonRow}>
<Button minHeight={42} minWidth={140} onClick={onClose}>
Exit

View File

@ -1,5 +1,5 @@
import { createStyles } from '@material-ui/core'
import { border, lg, md, sm } from 'src/theme/variables'
import { background, border, lg, md, sm } from 'src/theme/variables'
export const styles = createStyles({
heading: {
@ -30,4 +30,8 @@ export const styles = createStyles({
marginTop: sm,
fontSize: md,
},
gasCostsContainer: {
backgroundColor: background,
padding: `0 ${lg}`,
},
})

View File

@ -6,7 +6,7 @@ import {
EthHashInfo,
IconText,
} from '@gnosis.pm/safe-react-components'
import styled from 'styled-components'
import styled, { css } from 'styled-components'
export const Wrapper = styled.div`
display: flex;
@ -139,16 +139,16 @@ export const GroupedTransactionsCard = styled(StyledTransactions)`
}
`
const gridColumns = {
nonce: '0.75fr',
nonce: '0.5fr',
type: '3fr',
info: '3fr',
time: '1.5fr',
time: '2.5fr',
votes: '1.5fr',
actions: '1fr',
status: '3fr',
status: '2.5fr',
}
export const WillBeReplaced = styled.div`
const willBeReplaced = css`
.will-be-replaced * {
color: gray !important;
text-decoration: line-through !important;
@ -156,7 +156,18 @@ export const WillBeReplaced = styled.div`
}
`
export const StyledTransaction = styled(WillBeReplaced)`
const failedTransaction = css`
&.failed-transaction {
div[class^='tx-']:not(.tx-status):not(.tx-nonce) {
opacity: 0.5;
}
}
`
export const StyledTransaction = styled.div`
${willBeReplaced};
${failedTransaction};
display: grid;
grid-template-columns: ${Object.values(gridColumns).join(' ')};
width: 100%;
@ -165,6 +176,10 @@ export const StyledTransaction = styled(WillBeReplaced)`
align-self: center;
}
.tx-votes {
justify-self: center;
}
.tx-actions {
visibility: hidden;
justify-self: end;
@ -208,7 +223,7 @@ export const GroupedTransactions = styled(StyledTransaction)`
// builds the tree-view layout
.tree-lines {
height: 100%;
margin-left: 50%;
margin-left: 30px;
position: relative;
width: 30%;
@ -286,8 +301,8 @@ export const DisclaimerContainer = styled(StyledTransaction)`
background-color: ${({ theme }) => theme.colors.inputField} !important;
border-radius: 4px;
margin: 12px 8px 0 12px;
padding: 8px;
width: calc(100% - 40px);
padding: 8px 12px;
width: calc(100% - 48px);
.nonce {
grid-column-start: 1;
@ -299,13 +314,15 @@ export const DisclaimerContainer = styled(StyledTransaction)`
}
`
export const TxDetailsContainer = styled(WillBeReplaced)`
export const TxDetailsContainer = styled.div`
${willBeReplaced};
background-color: ${({ theme }) => theme.colors.separator} !important;
column-gap: 2px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-auto-rows: minmax(min-content, max-content);
grid-template-rows: [tx-summary] minmax(min-content, max-content) [tx-details] minmax(100px, 1fr);
grid-template-rows: [tx-summary] minmax(min-content, max-content) [tx-details] minmax(min-content, 1fr);
row-gap: 2px;
width: 100%;

View File

@ -1,5 +1,4 @@
import { BigNumber } from 'bignumber.js'
import format from 'date-fns/format'
import { getNetworkInfo } from 'src/config'
import {
@ -25,10 +24,6 @@ export const TX_TABLE_RAW_TX_ID = 'tx'
export const TX_TABLE_RAW_CANCEL_TX_ID = 'cancelTx'
export const TX_TABLE_EXPAND_ICON = 'expand'
export const formatTime = (timestamp: number): string => format(timestamp, 'h:mm a')
export const formatDateTime = (timestamp: number): string => format(timestamp, 'MMM d, yyyy - h:mm:ss a')
export const NOT_AVAILABLE = 'n/a'
interface AmountData {

View File

@ -203,7 +203,7 @@ export const EditTxParametersForm = ({
</EthereumOptions>
<StyledLink
href="https://docs.gnosis.io/safe/docs/contracts_tx_execution/#safe-transaction-gas-limit-estimation"
href="https://help.gnosis-safe.io/en/articles/4738445-configure-advanced-transaction-parameters-manually"
target="_blank"
>
<Text size="xl" color="primary">

View File

@ -1,4 +1,6 @@
import { formatRelative } from 'date-fns'
import format from 'date-fns/format'
import formatRelative from 'date-fns/formatRelative'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
export const relativeTime = (baseTimeMin: string, resetTimeMin: string): string => {
if (resetTimeMin === '0') {
@ -17,3 +19,17 @@ export const getUTCStartOfDate = (timestamp: number): number => {
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0, 0)
}
export const getLocalStartOfDate = (timestamp: number): number => {
const date = new Date(timestamp)
return date.setHours(0, 0, 0, 0)
}
export const formatWithSchema = (timestamp: number, schema: string): string => format(timestamp, schema)
export const formatTime = (timestamp: number): string => formatWithSchema(timestamp, 'h:mm a')
export const formatDateTime = (timestamp: number): string => formatWithSchema(timestamp, 'MMM d, yyyy - h:mm:ss a')
export const formatTimeInWords = (timestamp: number): string => formatDistanceToNow(timestamp, { addSuffix: true })