mirror of
https://github.com/status-im/safe-react.git
synced 2025-02-23 15:08:05 +00:00
Merge branch 'development' of github.com:gnosis/safe-react into development
This commit is contained in:
commit
530ba102bf
@ -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": {
|
||||
|
@ -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 && (
|
||||
|
@ -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
|
||||
|
@ -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>()
|
||||
|
@ -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,
|
||||
|
@ -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}>
|
||||
|
@ -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>
|
||||
</>
|
||||
)
|
||||
|
@ -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} />
|
||||
|
@ -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)}>
|
||||
|
@ -60,13 +60,14 @@ 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}
|
||||
@ -75,7 +76,6 @@ export const MultiSendDetails = ({ txData }: { txData: TransactionData }): React
|
||||
{details}
|
||||
</MultiSendTxGroup>
|
||||
)
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
|
@ -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)}
|
||||
|
@ -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}
|
||||
|
@ -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,6 +47,7 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
|
||||
return (
|
||||
<>
|
||||
{
|
||||
<Tooltip title={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'} placement="top">
|
||||
<IconButton
|
||||
size="small"
|
||||
type="button"
|
||||
@ -40,18 +56,16 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
>
|
||||
<Icon
|
||||
type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'}
|
||||
color="primary"
|
||||
size="sm"
|
||||
tooltip={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'}
|
||||
/>
|
||||
<Icon type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'} color="primary" size="sm" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{canCancel && (
|
||||
<Tooltip title="Cancel" placement="top">
|
||||
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
|
||||
<Icon type="circleCross" color="error" size="sm" tooltip="Cancel" />
|
||||
<Icon type="circleCross" color="error" size="sm" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 => {
|
||||
|
@ -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 = () => {
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
() =>
|
||||
|
@ -380,7 +380,10 @@ export const ApproveTxModal = ({
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
</Block>
|
||||
|
||||
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||
<Block className={classes.gasCostsContainer}>
|
||||
<TransactionFees
|
||||
gasCostFormatted={gasCostFormatted}
|
||||
isExecution={isExecution}
|
||||
@ -389,6 +392,7 @@ export const ApproveTxModal = ({
|
||||
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
||||
/>
|
||||
</Block>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<Row align="center" className={classes.buttonRow}>
|
||||
|
@ -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>
|
||||
)}
|
||||
<Row align="center" className={classes.buttonRow}>
|
||||
<Button minHeight={42} minWidth={140} onClick={onClose}>
|
||||
Exit
|
||||
|
@ -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}`,
|
||||
},
|
||||
})
|
||||
|
@ -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%;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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">
|
||||
|
@ -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 })
|
||||
|
Loading…
x
Reference in New Issue
Block a user