mirror of
https://github.com/status-im/safe-react.git
synced 2025-02-23 23:18:06 +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",
|
"name": "safe-react",
|
||||||
"version": "2.19.2",
|
"version": "3.0.0",
|
||||||
"description": "Allowing crypto users manage funds in a safer way",
|
"description": "Allowing crypto users manage funds in a safer way",
|
||||||
"website": "https://github.com/gnosis/safe-react#readme",
|
"website": "https://github.com/gnosis/safe-react#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Checkbox from '@material-ui/core/Checkbox'
|
import Checkbox from '@material-ui/core/Checkbox'
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
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 { useDispatch, useSelector } from 'react-redux'
|
||||||
import Button from 'src/components/layout/Button'
|
import Button from 'src/components/layout/Button'
|
||||||
import Link from 'src/components/layout/Link'
|
import Link from 'src/components/layout/Link'
|
||||||
@ -96,7 +96,7 @@ interface CookiesBannerFormProps {
|
|||||||
|
|
||||||
const CookiesBanner = (): ReactElement => {
|
const CookiesBanner = (): ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useRef(useDispatch())
|
||||||
|
|
||||||
const [showAnalytics, setShowAnalytics] = useState(false)
|
const [showAnalytics, setShowAnalytics] = useState(false)
|
||||||
const [showIntercom, setShowIntercom] = useState(false)
|
const [showIntercom, setShowIntercom] = useState(false)
|
||||||
@ -110,7 +110,7 @@ const CookiesBanner = (): ReactElement => {
|
|||||||
async function fetchCookiesFromStorage() {
|
async function fetchCookiesFromStorage() {
|
||||||
const cookiesState = await loadFromCookie(COOKIES_KEY)
|
const cookiesState = await loadFromCookie(COOKIES_KEY)
|
||||||
if (!cookiesState) {
|
if (!cookiesState) {
|
||||||
dispatch(openCookieBanner({ cookieBannerOpen: true }))
|
dispatch.current(openCookieBanner({ cookieBannerOpen: true }))
|
||||||
} else {
|
} else {
|
||||||
const { acceptedIntercom, acceptedAnalytics, acceptedNecessary } = cookiesState
|
const { acceptedIntercom, acceptedAnalytics, acceptedNecessary } = cookiesState
|
||||||
if (acceptedIntercom === undefined) {
|
if (acceptedIntercom === undefined) {
|
||||||
@ -143,7 +143,7 @@ const CookiesBanner = (): ReactElement => {
|
|||||||
await saveCookie(COOKIES_KEY, newState, 365)
|
await saveCookie(COOKIES_KEY, newState, 365)
|
||||||
setShowAnalytics(!isDesktop)
|
setShowAnalytics(!isDesktop)
|
||||||
setShowIntercom(true)
|
setShowIntercom(true)
|
||||||
dispatch(openCookieBanner({ cookieBannerOpen: false }))
|
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeCookiesBannerHandler = async () => {
|
const closeCookiesBannerHandler = async () => {
|
||||||
@ -159,7 +159,7 @@ const CookiesBanner = (): ReactElement => {
|
|||||||
if (!localIntercom && isIntercomLoaded()) {
|
if (!localIntercom && isIntercomLoaded()) {
|
||||||
closeIntercom()
|
closeIntercom()
|
||||||
}
|
}
|
||||||
dispatch(openCookieBanner({ cookieBannerOpen: false }))
|
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAnalytics && !isDesktop) {
|
if (showAnalytics && !isDesktop) {
|
||||||
@ -254,7 +254,7 @@ const CookiesBanner = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
className={classes.intercomImage}
|
className={classes.intercomImage}
|
||||||
src={IntercomIcon}
|
src={IntercomIcon}
|
||||||
onClick={() => dispatch(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))}
|
onClick={() => dispatch.current(openCookieBanner({ cookieBannerOpen: true, intercomAlertDisplayed: true }))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isDesktop && showBanner?.cookieBannerOpen && (
|
{!isDesktop && showBanner?.cookieBannerOpen && (
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
import { UPDATE_TRANSACTION_DETAILS } from 'src/logic/safe/store/actions/fetchTransactionDetails'
|
import { UPDATE_TRANSACTION_DETAILS } from 'src/logic/safe/store/actions/fetchTransactionDetails'
|
||||||
|
|
||||||
import { AppReduxState } from 'src/store'
|
import { AppReduxState } from 'src/store'
|
||||||
import { getUTCStartOfDate } from 'src/utils/date'
|
import { getLocalStartOfDate } from 'src/utils/date'
|
||||||
import { sameString } from 'src/utils/strings'
|
import { sameString } from 'src/utils/strings'
|
||||||
import { sortObject } from 'src/utils/objects'
|
import { sortObject } from 'src/utils/objects'
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ export const gatewayTransactions = handleActions<AppReduxState['gatewayTransacti
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isTransactionSummary(value)) {
|
if (isTransactionSummary(value)) {
|
||||||
const startOfDate = getUTCStartOfDate(value.transaction.timestamp)
|
const startOfDate = getLocalStartOfDate(value.transaction.timestamp)
|
||||||
|
|
||||||
if (typeof history[startOfDate] === 'undefined') {
|
if (typeof history[startOfDate] === 'undefined') {
|
||||||
history[startOfDate] = []
|
history[startOfDate] = []
|
||||||
@ -326,6 +326,11 @@ export const gatewayTransactions = handleActions<AppReduxState['gatewayTransacti
|
|||||||
}
|
}
|
||||||
case 'queued.next': {
|
case 'queued.next': {
|
||||||
queued.next[nonce] = queued.next[nonce].map((txToUpdate) => {
|
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 (typeof id !== 'undefined') {
|
||||||
if (sameString(txToUpdate.id, id)) {
|
if (sameString(txToUpdate.id, id)) {
|
||||||
txToUpdate.txStatus = txStatus
|
txToUpdate.txStatus = txStatus
|
||||||
@ -339,6 +344,11 @@ export const gatewayTransactions = handleActions<AppReduxState['gatewayTransacti
|
|||||||
}
|
}
|
||||||
case 'queued.queued': {
|
case 'queued.queued': {
|
||||||
queued.queued[nonce] = queued.queued[nonce].map((txToUpdate) => {
|
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 (typeof id !== 'undefined') {
|
||||||
if (sameString(txToUpdate.id, id)) {
|
if (sameString(txToUpdate.id, id)) {
|
||||||
txToUpdate.txStatus = txStatus
|
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 { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||||
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES, CALL } from 'src/logic/safe/transactions'
|
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES, CALL } from 'src/logic/safe/transactions'
|
||||||
import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend'
|
import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend'
|
||||||
|
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||||
|
|
||||||
import GasEstimationInfo from './GasEstimationInfo'
|
import GasEstimationInfo from './GasEstimationInfo'
|
||||||
import { getNetworkInfo } from 'src/config'
|
import { getNetworkInfo } from 'src/config'
|
||||||
@ -105,6 +106,10 @@ type OwnProps = {
|
|||||||
|
|
||||||
const { nativeCoin } = getNetworkInfo()
|
const { nativeCoin } = getNetworkInfo()
|
||||||
|
|
||||||
|
const parseTxValue = (value: string | number): string => {
|
||||||
|
return web3ReadOnly.utils.toBN(value).toString()
|
||||||
|
}
|
||||||
|
|
||||||
export const ConfirmTransactionModal = ({
|
export const ConfirmTransactionModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
app,
|
app,
|
||||||
@ -122,7 +127,10 @@ export const ConfirmTransactionModal = ({
|
|||||||
|
|
||||||
const txRecipient: string | undefined = useMemo(() => (txs.length > 1 ? MULTI_SEND_ADDRESS : txs[0]?.to), [txs])
|
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 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 operation = useMemo(() => (txs.length > 1 ? DELEGATE_CALL : CALL), [txs])
|
||||||
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
|
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
|
||||||
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
|
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
|
||||||
|
@ -86,13 +86,13 @@ export const RemoveModuleModal = ({ onClose, selectedModulePair }: RemoveModuleM
|
|||||||
|
|
||||||
const removeSelectedModule = async (txParameters: TxParameters): Promise<void> => {
|
const removeSelectedModule = async (txParameters: TxParameters): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
dispatch(
|
await dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
safeAddress,
|
safeAddress,
|
||||||
to: safeAddress,
|
to: safeAddress,
|
||||||
valueInWei: '0',
|
valueInWei: '0',
|
||||||
txData,
|
txData,
|
||||||
txNonce: txParameters.ethNonce,
|
txNonce: txParameters.safeNonce,
|
||||||
safeTxGas: txParameters.safeTxGas ? Number(txParameters.safeTxGas) : undefined,
|
safeTxGas: txParameters.safeTxGas ? Number(txParameters.safeTxGas) : undefined,
|
||||||
ethParameters: txParameters,
|
ethParameters: txParameters,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
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 { makeStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
|
|
||||||
import Field from 'src/components/forms/Field'
|
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 { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
|
||||||
import { TransactionFees } from 'src/components/TransactionsFees'
|
import { TransactionFees } from 'src/components/TransactionsFees'
|
||||||
import { TxParametersDetail } from 'src/routes/safe/components/Transactions/helpers/TxParametersDetail'
|
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 { styles } from './style'
|
||||||
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
|
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
|
||||||
@ -30,7 +33,6 @@ const THRESHOLD_FIELD_NAME = 'threshold'
|
|||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
type ChangeThresholdModalProps = {
|
type ChangeThresholdModalProps = {
|
||||||
onChangeThreshold: (newThreshold: number) => void
|
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
owners?: List<SafeOwner>
|
owners?: List<SafeOwner>
|
||||||
safeAddress: string
|
safeAddress: string
|
||||||
@ -38,13 +40,13 @@ type ChangeThresholdModalProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ChangeThresholdModal = ({
|
export const ChangeThresholdModal = ({
|
||||||
onChangeThreshold,
|
|
||||||
onClose,
|
onClose,
|
||||||
owners,
|
owners,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
threshold = 1,
|
threshold = 1,
|
||||||
}: ChangeThresholdModalProps): React.ReactElement => {
|
}: ChangeThresholdModalProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const dispatch = useDispatch()
|
||||||
const [data, setData] = useState('')
|
const [data, setData] = useState('')
|
||||||
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
|
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
|
||||||
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
|
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
|
||||||
@ -83,11 +85,21 @@ export const ChangeThresholdModal = ({
|
|||||||
|
|
||||||
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
|
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = async ({ txParameters }) => {
|
||||||
const newThreshold = values[THRESHOLD_FIELD_NAME]
|
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()
|
onClose()
|
||||||
onChangeThreshold(newThreshold)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeEditModalCallback = (txParameters: TxParameters) => {
|
const closeEditModalCallback = (txParameters: TxParameters) => {
|
||||||
@ -123,7 +135,7 @@ export const ChangeThresholdModal = ({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<GnoForm initialValues={{ threshold: threshold.toString() }} onSubmit={handleSubmit}>
|
<GnoForm initialValues={{ threshold: threshold.toString(), txParameters }} onSubmit={handleSubmit}>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.modalContent}>
|
<Block className={classes.modalContent}>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import Modal from 'src/components/Modal'
|
import Modal from 'src/components/Modal'
|
||||||
import Block from 'src/components/layout/Block'
|
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 Heading from 'src/components/layout/Heading'
|
||||||
import Paragraph from 'src/components/layout/Paragraph'
|
import Paragraph from 'src/components/layout/Paragraph'
|
||||||
import Row from 'src/components/layout/Row'
|
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 { grantedSelector } from 'src/routes/safe/container/selector'
|
||||||
import { createTransaction } from 'src/logic/safe/store/actions/createTransaction'
|
|
||||||
import {
|
import {
|
||||||
safeOwnersSelector,
|
safeOwnersSelector,
|
||||||
safeParamAddressFromStateSelector,
|
safeParamAddressFromStateSelector,
|
||||||
safeThresholdSelector,
|
safeThresholdSelector,
|
||||||
} from 'src/logic/safe/store/selectors'
|
} from 'src/logic/safe/store/selectors'
|
||||||
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
|
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 { ChangeThresholdModal } from './ChangeThreshold'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
@ -30,46 +25,21 @@ const useStyles = makeStyles(styles)
|
|||||||
const ThresholdSettings = (): React.ReactElement => {
|
const ThresholdSettings = (): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [isModalOpen, setModalOpen] = useState(false)
|
const [isModalOpen, setModalOpen] = useState(false)
|
||||||
const dispatch = useDispatch()
|
|
||||||
const threshold = useSelector(safeThresholdSelector) || 1
|
const threshold = useSelector(safeThresholdSelector) || 1
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
const owners = useSelector(safeOwnersSelector)
|
const owners = useSelector(safeOwnersSelector)
|
||||||
const granted = useSelector(grantedSelector)
|
const granted = useSelector(grantedSelector)
|
||||||
const txParameters = useTransactionParameters()
|
|
||||||
const [activeScreen, setActiveScreen] = useState('form')
|
|
||||||
|
|
||||||
const toggleModal = () => {
|
const toggleModal = () => {
|
||||||
setModalOpen((prevOpen) => !prevOpen)
|
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()
|
const { trackEvent } = useAnalytics()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
|
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
|
||||||
}, [trackEvent])
|
}, [trackEvent])
|
||||||
|
|
||||||
const closeEditTxParameters = () => setActiveScreen('form')
|
|
||||||
|
|
||||||
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.container}>
|
<Block className={classes.container}>
|
||||||
@ -98,22 +68,7 @@ const ThresholdSettings = (): React.ReactElement => {
|
|||||||
open={isModalOpen}
|
open={isModalOpen}
|
||||||
title="Change Required Confirmations"
|
title="Change Required Confirmations"
|
||||||
>
|
>
|
||||||
{activeScreen === 'form' && (
|
<ChangeThresholdModal onClose={toggleModal} owners={owners} safeAddress={safeAddress} threshold={threshold} />
|
||||||
<ChangeThresholdModal
|
|
||||||
onChangeThreshold={onChangeThreshold}
|
|
||||||
onClose={toggleModal}
|
|
||||||
owners={owners}
|
|
||||||
safeAddress={safeAddress}
|
|
||||||
threshold={threshold}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{activeScreen === 'editTxParameters' && (
|
|
||||||
<EditTxParametersForm
|
|
||||||
txParameters={txParameters}
|
|
||||||
onClose={closeEditTxParameters}
|
|
||||||
parametersStatus={getParametersStatus()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Loader } from '@gnosis.pm/safe-react-components'
|
import { Loader } from '@gnosis.pm/safe-react-components'
|
||||||
import { format } from 'date-fns'
|
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
|
|
||||||
import { InfiniteScroll, SCROLLABLE_TARGET_ID } from 'src/components/InfiniteScroll'
|
import { InfiniteScroll, SCROLLABLE_TARGET_ID } from 'src/components/InfiniteScroll'
|
||||||
@ -13,6 +12,7 @@ import {
|
|||||||
} from './styled'
|
} from './styled'
|
||||||
import { TxHistoryRow } from './TxHistoryRow'
|
import { TxHistoryRow } from './TxHistoryRow'
|
||||||
import { TxLocationContext } from './TxLocationProvider'
|
import { TxLocationContext } from './TxLocationProvider'
|
||||||
|
import { formatWithSchema } from 'src/utils/date'
|
||||||
|
|
||||||
export const HistoryTxList = (): ReactElement => {
|
export const HistoryTxList = (): ReactElement => {
|
||||||
const { count, hasMore, next, transactions } = usePagedHistoryTransactions()
|
const { count, hasMore, next, transactions } = usePagedHistoryTransactions()
|
||||||
@ -31,7 +31,7 @@ export const HistoryTxList = (): ReactElement => {
|
|||||||
<InfiniteScroll dataLength={transactions.length} next={next} hasMore={hasMore}>
|
<InfiniteScroll dataLength={transactions.length} next={next} hasMore={hasMore}>
|
||||||
{transactions?.map(([timestamp, txs]) => (
|
{transactions?.map(([timestamp, txs]) => (
|
||||||
<StyledTransactionsGroup key={timestamp}>
|
<StyledTransactionsGroup key={timestamp}>
|
||||||
<SubTitle size="lg">{format(Number(timestamp), 'MMM d, yyyy')}</SubTitle>
|
<SubTitle size="lg">{formatWithSchema(Number(timestamp), 'MMM d, yyyy')}</SubTitle>
|
||||||
<StyledTransactions>
|
<StyledTransactions>
|
||||||
{txs.map((transaction) => (
|
{txs.map((transaction) => (
|
||||||
<TxHistoryRow key={transaction.id} transaction={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 { DataDecoded } from 'src/logic/safe/store/models/types/gateway.d'
|
||||||
import { isArrayParameter } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils'
|
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'
|
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 }>`
|
const TxDetailsMethodParam = styled.div<{ isArrayParameter: boolean }>`
|
||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
display: ${({ isArrayParameter }) => (isArrayParameter ? 'block' : 'flex')};
|
display: ${({ isArrayParameter }) => (isArrayParameter ? 'block' : 'flex')};
|
||||||
@ -27,7 +17,7 @@ const TxDetailsMethodParam = styled.div<{ isArrayParameter: boolean }>`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const TxInfo = styled.div`
|
const TxInfo = styled.div`
|
||||||
padding: 8px 8px 8px 16px;
|
padding: 8px 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledMethodName = styled(Text)`
|
const StyledMethodName = styled(Text)`
|
||||||
@ -35,21 +25,11 @@ const StyledMethodName = styled(Text)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const MethodDetails = ({ data }: { data: DataDecoded }): React.ReactElement => {
|
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 (
|
return (
|
||||||
<TxInfo>
|
<TxInfo>
|
||||||
<TxDetailsMethodName size="xl" strong>
|
<Text size="xl" strong>
|
||||||
{data.method}
|
{data.method}
|
||||||
</TxDetailsMethodName>
|
</Text>
|
||||||
|
|
||||||
{data.parameters?.map((param, index) => (
|
{data.parameters?.map((param, index) => (
|
||||||
<TxDetailsMethodParam key={`${data.method}_param-${index}`} isArrayParameter={isArrayParameter(param.type)}>
|
<TxDetailsMethodParam key={`${data.method}_param-${index}`} isArrayParameter={isArrayParameter(param.type)}>
|
||||||
|
@ -60,21 +60,21 @@ export const MultiSendDetails = ({ txData }: { txData: TransactionData }): React
|
|||||||
const title = `Send ${amount} ${nativeCoin.name} to:`
|
const title = `Send ${amount} ${nativeCoin.name} to:`
|
||||||
|
|
||||||
if (dataDecoded) {
|
if (dataDecoded) {
|
||||||
|
// Backend decoded data
|
||||||
details = <MethodDetails data={dataDecoded} />
|
details = <MethodDetails data={dataDecoded} />
|
||||||
} else {
|
} else {
|
||||||
|
// We couldn't decode it but we have data
|
||||||
details = data && <HexEncodedData hexData={data} />
|
details = data && <HexEncodedData hexData={data} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
details && (
|
<MultiSendTxGroup
|
||||||
<MultiSendTxGroup
|
key={`${data ?? to}-${index}`}
|
||||||
key={`${data ?? to}-${index}`}
|
actionTitle={actionTitle}
|
||||||
actionTitle={actionTitle}
|
txDetails={{ title, address: to, dataDecoded }}
|
||||||
txDetails={{ title, address: to, dataDecoded }}
|
>
|
||||||
>
|
{details}
|
||||||
{details}
|
</MultiSendTxGroup>
|
||||||
</MultiSendTxGroup>
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
|
@ -11,7 +11,7 @@ export const OwnerRow = ({ ownerAddress }: { ownerAddress: string }): ReactEleme
|
|||||||
return (
|
return (
|
||||||
<EthHashInfo
|
<EthHashInfo
|
||||||
hash={ownerAddress}
|
hash={ownerAddress}
|
||||||
name={ownerName}
|
name={ownerName === 'UNKNOWN' ? '' : ownerName}
|
||||||
showIdenticon
|
showIdenticon
|
||||||
showCopyBtn
|
showCopyBtn
|
||||||
explorerUrl={getExplorerInfo(ownerAddress)}
|
explorerUrl={getExplorerInfo(ownerAddress)}
|
||||||
|
@ -3,7 +3,8 @@ import { ThemeColors } from '@gnosis.pm/safe-react-components/dist/theme'
|
|||||||
import { Tooltip } from '@material-ui/core'
|
import { Tooltip } from '@material-ui/core'
|
||||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
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 CustomIconText from 'src/components/CustomIconText'
|
||||||
import {
|
import {
|
||||||
@ -13,15 +14,16 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
} from 'src/logic/safe/store/models/types/gateway.d'
|
} from 'src/logic/safe/store/models/types/gateway.d'
|
||||||
import { TxCollapsedActions } from 'src/routes/safe/components/Transactions/GatewayTransactions/TxCollapsedActions'
|
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 { KNOWN_MODULES } from 'src/utils/constants'
|
||||||
import styled from 'styled-components'
|
import { sameString } from 'src/utils/strings'
|
||||||
import { AssetInfo, isTokenTransferAsset } from './hooks/useAssetInfo'
|
import { AssetInfo, isTokenTransferAsset } from './hooks/useAssetInfo'
|
||||||
import { TransactionActions } from './hooks/useTransactionActions'
|
import { TransactionActions } from './hooks/useTransactionActions'
|
||||||
import { TransactionStatusProps } from './hooks/useTransactionStatus'
|
import { TransactionStatusProps } from './hooks/useTransactionStatus'
|
||||||
import { TxTypeProps } from './hooks/useTransactionType'
|
import { TxTypeProps } from './hooks/useTransactionType'
|
||||||
import { StyledGroupedTransactions, StyledTransaction } from './styled'
|
import { StyledGroupedTransactions, StyledTransaction } from './styled'
|
||||||
import { TokenTransferAmount } from './TokenTransferAmount'
|
import { TokenTransferAmount } from './TokenTransferAmount'
|
||||||
|
import { TxLocationContext } from './TxLocationProvider'
|
||||||
import { CalculatedVotes } from './TxQueueCollapsed'
|
import { CalculatedVotes } from './TxQueueCollapsed'
|
||||||
|
|
||||||
const TxInfo = ({ info }: { info: AssetInfo }) => {
|
const TxInfo = ({ info }: { info: AssetInfo }) => {
|
||||||
@ -126,6 +128,8 @@ export const TxCollapsed = ({
|
|||||||
actions,
|
actions,
|
||||||
status,
|
status,
|
||||||
}: TxCollapsedProps): ReactElement => {
|
}: TxCollapsedProps): ReactElement => {
|
||||||
|
const { txLocation } = useContext(TxLocationContext)
|
||||||
|
|
||||||
const willBeReplaced = transaction?.txStatus === 'WILL_BE_REPLACED' ? ' will-be-replaced' : ''
|
const willBeReplaced = transaction?.txStatus === 'WILL_BE_REPLACED' ? ' will-be-replaced' : ''
|
||||||
|
|
||||||
const txCollapsedNonce = (
|
const txCollapsedNonce = (
|
||||||
@ -149,7 +153,7 @@ export const TxCollapsed = ({
|
|||||||
<div className={'tx-time' + willBeReplaced}>
|
<div className={'tx-time' + willBeReplaced}>
|
||||||
<Tooltip classes={tooltipStyles} title={formatDateTime(time)} arrow>
|
<Tooltip classes={tooltipStyles} title={formatDateTime(time)} arrow>
|
||||||
<TooltipContent ref={timestamp}>
|
<TooltipContent ref={timestamp}>
|
||||||
<Text size="xl">{formatTime(time)}</Text>
|
<Text size="xl">{txLocation === 'history' ? formatTime(time) : formatTimeInWords(time)}</Text>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -203,7 +207,7 @@ export const TxCollapsed = ({
|
|||||||
{txCollapsedStatus}
|
{txCollapsedStatus}
|
||||||
</StyledGroupedTransactions>
|
</StyledGroupedTransactions>
|
||||||
) : (
|
) : (
|
||||||
<StyledTransaction>
|
<StyledTransaction className={sameString(status.text, 'Failed') ? 'failed-transaction' : ''}>
|
||||||
{txCollapsedNonce}
|
{txCollapsedNonce}
|
||||||
{txCollapsedType}
|
{txCollapsedType}
|
||||||
{txCollapsedInfo}
|
{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 { default as MuiIconButton } from '@material-ui/core/IconButton'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { Transaction } from 'src/logic/safe/store/models/types/gateway.d'
|
import { Transaction } from 'src/logic/safe/store/models/types/gateway.d'
|
||||||
import { useActionButtonsHandlers } from './hooks/useActionButtonsHandlers'
|
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)`
|
const IconButton = styled(MuiIconButton)`
|
||||||
padding: 8px !important;
|
padding: 8px !important;
|
||||||
|
|
||||||
@ -32,26 +47,25 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
<IconButton
|
<Tooltip title={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'} placement="top">
|
||||||
size="small"
|
<IconButton
|
||||||
type="button"
|
size="small"
|
||||||
onClick={handleConfirmButtonClick}
|
type="button"
|
||||||
disabled={disabledActions}
|
onClick={handleConfirmButtonClick}
|
||||||
onMouseEnter={handleOnMouseEnter}
|
disabled={disabledActions}
|
||||||
onMouseLeave={handleOnMouseLeave}
|
onMouseEnter={handleOnMouseEnter}
|
||||||
>
|
onMouseLeave={handleOnMouseLeave}
|
||||||
<Icon
|
>
|
||||||
type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'}
|
<Icon type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'} color="primary" size="sm" />
|
||||||
color="primary"
|
</IconButton>
|
||||||
size="sm"
|
</Tooltip>
|
||||||
tooltip={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
}
|
}
|
||||||
{canCancel && (
|
{canCancel && (
|
||||||
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
|
<Tooltip title="Cancel" placement="top">
|
||||||
<Icon type="circleCross" color="error" size="sm" tooltip="Cancel" />
|
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
|
||||||
</IconButton>
|
<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 { sameString } from 'src/utils/strings'
|
||||||
import { HexEncodedData } from './HexEncodedData'
|
import { HexEncodedData } from './HexEncodedData'
|
||||||
import { MethodDetails } from './MethodDetails'
|
import { MethodDetails } from './MethodDetails'
|
||||||
import { MultiSendDetails } from './MultiSendDetails'
|
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 = {
|
type TxDataProps = {
|
||||||
txData: ExpandedTxDetails['txData']
|
txData: ExpandedTxDetails['txData']
|
||||||
}
|
}
|
||||||
@ -24,7 +50,11 @@ export const TxData = ({ txData }: TxDataProps): ReactElement | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we render the hex encoded data
|
// 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
|
// known data and particularly `multiSend` data type
|
||||||
@ -32,6 +62,20 @@ export const TxData = ({ txData }: TxDataProps): ReactElement | null => {
|
|||||||
return <MultiSendDetails txData={txData} />
|
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
|
// 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 React, { ReactElement } from 'react'
|
||||||
|
|
||||||
import { getExplorerInfo } from 'src/config'
|
import { getExplorerInfo } from 'src/config'
|
||||||
|
import { formatDateTime } from 'src/utils/date'
|
||||||
import { Creation, Transaction } from 'src/logic/safe/store/models/types/gateway.d'
|
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'
|
import { InlineEthHashInfo, TxDetailsContainer } from './styled'
|
||||||
|
|
||||||
export const TxInfoCreation = ({ transaction }: { transaction: Transaction }): ReactElement | null => {
|
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 knownAddress = recipientName !== 'UNKNOWN'
|
||||||
|
|
||||||
const { txLocation } = useContext<TxLocationProps>(TxLocationContext)
|
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 [sendModalOpen, setSendModalOpen] = useState(false)
|
||||||
const sendModalOpenHandler = () => {
|
const sendModalOpenHandler = () => {
|
||||||
|
@ -2,9 +2,10 @@ import { Text } from '@gnosis.pm/safe-react-components'
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
|
|
||||||
import { getExplorerInfo } from 'src/config'
|
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 { ExpandedTxDetails, isMultiSigExecutionDetails, Operation } from 'src/logic/safe/store/models/types/gateway.d'
|
||||||
import { InlineEthHashInfo } from './styled'
|
import { InlineEthHashInfo } from './styled'
|
||||||
import { formatDateTime, NOT_AVAILABLE } from './utils'
|
import { NOT_AVAILABLE } from './utils'
|
||||||
|
|
||||||
export const TxSummary = ({ txDetails }: { txDetails: ExpandedTxDetails }): ReactElement => {
|
export const TxSummary = ({ txDetails }: { txDetails: ExpandedTxDetails }): ReactElement => {
|
||||||
const { txHash, detailedExecutionInfo, executedAt, txData } = txDetails
|
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 isPending = useMemo(() => !!transaction.txStatus.match(/^PENDING.*/), [transaction.txStatus])
|
||||||
|
|
||||||
const signaturePending = useCallback(addressInList(transaction.executionInfo?.missingSigners), [])
|
const signaturePending = addressInList(transaction.executionInfo?.missingSigners)
|
||||||
|
|
||||||
const disabledActions = useMemo(
|
const disabledActions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -380,16 +380,20 @@ export const ApproveTxModal = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<TransactionFees
|
|
||||||
gasCostFormatted={gasCostFormatted}
|
|
||||||
isExecution={isExecution}
|
|
||||||
isCreation={isCreation}
|
|
||||||
isOffChainSignature={isOffChainSignature}
|
|
||||||
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
|
||||||
/>
|
|
||||||
</Block>
|
</Block>
|
||||||
|
|
||||||
|
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||||
|
<Block className={classes.gasCostsContainer}>
|
||||||
|
<TransactionFees
|
||||||
|
gasCostFormatted={gasCostFormatted}
|
||||||
|
isExecution={isExecution}
|
||||||
|
isCreation={isCreation}
|
||||||
|
isOffChainSignature={isOffChainSignature}
|
||||||
|
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
||||||
|
/>
|
||||||
|
</Block>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minHeight={42} minWidth={140} onClick={onClose}>
|
<Button minHeight={42} minWidth={140} onClick={onClose}>
|
||||||
|
@ -117,12 +117,14 @@ export const RejectTxModal = ({ isOpen, onClose, gwTransaction }: Props): React.
|
|||||||
<TxParametersDetail
|
<TxParametersDetail
|
||||||
txParameters={txParameters}
|
txParameters={txParameters}
|
||||||
onEdit={toggleEditMode}
|
onEdit={toggleEditMode}
|
||||||
compact={false}
|
|
||||||
parametersStatus={getParametersStatus()}
|
parametersStatus={getParametersStatus()}
|
||||||
isTransactionCreation={isCreation}
|
isTransactionCreation={isCreation}
|
||||||
isTransactionExecution={isExecution}
|
isTransactionExecution={isExecution}
|
||||||
/>
|
/>
|
||||||
<Row>
|
</Block>
|
||||||
|
|
||||||
|
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||||
|
<Block className={classes.gasCostsContainer}>
|
||||||
<TransactionFees
|
<TransactionFees
|
||||||
gasCostFormatted={gasCostFormatted}
|
gasCostFormatted={gasCostFormatted}
|
||||||
isExecution={isExecution}
|
isExecution={isExecution}
|
||||||
@ -130,8 +132,8 @@ export const RejectTxModal = ({ isOpen, onClose, gwTransaction }: Props): React.
|
|||||||
isOffChainSignature={isOffChainSignature}
|
isOffChainSignature={isOffChainSignature}
|
||||||
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Block>
|
||||||
</Block>
|
)}
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minHeight={42} minWidth={140} onClick={onClose}>
|
<Button minHeight={42} minWidth={140} onClick={onClose}>
|
||||||
Exit
|
Exit
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createStyles } from '@material-ui/core'
|
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({
|
export const styles = createStyles({
|
||||||
heading: {
|
heading: {
|
||||||
@ -30,4 +30,8 @@ export const styles = createStyles({
|
|||||||
marginTop: sm,
|
marginTop: sm,
|
||||||
fontSize: md,
|
fontSize: md,
|
||||||
},
|
},
|
||||||
|
gasCostsContainer: {
|
||||||
|
backgroundColor: background,
|
||||||
|
padding: `0 ${lg}`,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
EthHashInfo,
|
EthHashInfo,
|
||||||
IconText,
|
IconText,
|
||||||
} from '@gnosis.pm/safe-react-components'
|
} from '@gnosis.pm/safe-react-components'
|
||||||
import styled from 'styled-components'
|
import styled, { css } from 'styled-components'
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
export const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -139,16 +139,16 @@ export const GroupedTransactionsCard = styled(StyledTransactions)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
const gridColumns = {
|
const gridColumns = {
|
||||||
nonce: '0.75fr',
|
nonce: '0.5fr',
|
||||||
type: '3fr',
|
type: '3fr',
|
||||||
info: '3fr',
|
info: '3fr',
|
||||||
time: '1.5fr',
|
time: '2.5fr',
|
||||||
votes: '1.5fr',
|
votes: '1.5fr',
|
||||||
actions: '1fr',
|
actions: '1fr',
|
||||||
status: '3fr',
|
status: '2.5fr',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WillBeReplaced = styled.div`
|
const willBeReplaced = css`
|
||||||
.will-be-replaced * {
|
.will-be-replaced * {
|
||||||
color: gray !important;
|
color: gray !important;
|
||||||
text-decoration: line-through !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;
|
display: grid;
|
||||||
grid-template-columns: ${Object.values(gridColumns).join(' ')};
|
grid-template-columns: ${Object.values(gridColumns).join(' ')};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -165,6 +176,10 @@ export const StyledTransaction = styled(WillBeReplaced)`
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tx-votes {
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
.tx-actions {
|
.tx-actions {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
@ -208,7 +223,7 @@ export const GroupedTransactions = styled(StyledTransaction)`
|
|||||||
// builds the tree-view layout
|
// builds the tree-view layout
|
||||||
.tree-lines {
|
.tree-lines {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: 50%;
|
margin-left: 30px;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
|
|
||||||
@ -286,8 +301,8 @@ export const DisclaimerContainer = styled(StyledTransaction)`
|
|||||||
background-color: ${({ theme }) => theme.colors.inputField} !important;
|
background-color: ${({ theme }) => theme.colors.inputField} !important;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 12px 8px 0 12px;
|
margin: 12px 8px 0 12px;
|
||||||
padding: 8px;
|
padding: 8px 12px;
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 48px);
|
||||||
|
|
||||||
.nonce {
|
.nonce {
|
||||||
grid-column-start: 1;
|
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;
|
background-color: ${({ theme }) => theme.colors.separator} !important;
|
||||||
column-gap: 2px;
|
column-gap: 2px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-auto-rows: minmax(min-content, max-content);
|
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;
|
row-gap: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import format from 'date-fns/format'
|
|
||||||
|
|
||||||
import { getNetworkInfo } from 'src/config'
|
import { getNetworkInfo } from 'src/config'
|
||||||
import {
|
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_RAW_CANCEL_TX_ID = 'cancelTx'
|
||||||
export const TX_TABLE_EXPAND_ICON = 'expand'
|
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'
|
export const NOT_AVAILABLE = 'n/a'
|
||||||
|
|
||||||
interface AmountData {
|
interface AmountData {
|
||||||
|
@ -203,7 +203,7 @@ export const EditTxParametersForm = ({
|
|||||||
</EthereumOptions>
|
</EthereumOptions>
|
||||||
|
|
||||||
<StyledLink
|
<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"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Text size="xl" color="primary">
|
<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 => {
|
export const relativeTime = (baseTimeMin: string, resetTimeMin: string): string => {
|
||||||
if (resetTimeMin === '0') {
|
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)
|
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