diff --git a/ui/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/pages/swaps/awaiting-swap/awaiting-swap.js index dcb3a08b1..5456c1067 100644 --- a/ui/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/pages/swaps/awaiting-swap/awaiting-swap.js @@ -17,7 +17,6 @@ import { getUSDConversionRate, isHardwareWallet, getHardwareWalletType, - getSwapsDefaultToken, } from '../../../selectors'; import { @@ -34,10 +33,8 @@ import { getCurrentSmartTransactionsEnabled, getFromTokenInputValue, getMaxSlippage, - setSwapsFromToken, } from '../../../ducks/swaps/swaps'; import Mascot from '../../../components/ui/mascot'; -import Box from '../../../components/ui/box'; import { QUOTES_EXPIRED_ERROR, SWAP_FAILED_ERROR, @@ -56,10 +53,11 @@ import { stopPollingForQuotes } from '../../../store/actions'; import { getRenderableNetworkFeesForQuote } from '../swaps.util'; import SwapsFooter from '../swaps-footer'; +import CreateNewSwap from '../create-new-swap'; +import ViewOnBlockExplorer from '../view-on-block-explorer'; import SwapFailureIcon from './swap-failure-icon'; import SwapSuccessIcon from './swap-success-icon'; import QuotesTimeoutIcon from './quotes-timeout-icon'; -import ViewOnEtherScanLink from './view-on-ether-scan-link'; export default function AwaitingSwap({ swapComplete, @@ -85,8 +83,6 @@ export default function AwaitingSwap({ const usdConversionRate = useSelector(getUSDConversionRate); const chainId = useSelector(getCurrentChainId); const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual); - const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual); - const [trackedQuotesExpiredEvent, setTrackedQuotesExpiredEvent] = useState( false, ); @@ -141,11 +137,6 @@ export default function AwaitingSwap({ { blockExplorerUrl: baseNetworkUrl }, ); - const isCustomBlockExplorerUrl = Boolean( - SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] || - rpcPrefs.blockExplorerUrl, - ); - let headerText; let statusImage; let descriptionText; @@ -173,10 +164,9 @@ export default function AwaitingSwap({ submitText = t('tryAgain'); statusImage = ; content = blockExplorerUrl && ( - ); } else if (errorKey === QUOTES_EXPIRED_ERROR) { @@ -221,10 +211,9 @@ export default function AwaitingSwap({ , ]); content = blockExplorerUrl && ( - ); } else if (!errorKey && swapComplete) { @@ -240,35 +229,13 @@ export default function AwaitingSwap({ , ]); content = blockExplorerUrl && ( - ); } - const MakeAnotherSwap = () => { - return ( - - { - trackEvent({ - event: 'Make Another Swap', - category: EVENT.CATEGORIES.SWAPS, - sensitiveProperties, - }); - await dispatch(navigateBackToBuildQuote(history)); - dispatch(setSwapsFromToken(defaultSwapsToken)); - }} - > - {t('makeAnotherSwap')} - - - ); - }; - useEffect(() => { if (errorKey) { // If there was an error, stop polling for quotes. @@ -291,7 +258,9 @@ export default function AwaitingSwap({
{descriptionText}
{content} - {!errorKey && swapComplete ? : null} + {!errorKey && swapComplete ? ( + + ) : null} { if (errorKey === OFFLINE_FOR_MAINTENANCE) { diff --git a/ui/pages/swaps/awaiting-swap/index.scss b/ui/pages/swaps/awaiting-swap/index.scss index 0750ace6f..2dd865fd3 100644 --- a/ui/pages/swaps/awaiting-swap/index.scss +++ b/ui/pages/swaps/awaiting-swap/index.scss @@ -20,10 +20,6 @@ justify-content: center; } - a { - color: var(--color-primary-default); - } - &__status-image { margin-top: 12px; margin-bottom: 16px; @@ -64,7 +60,6 @@ font-weight: bold; } - &__view-on-etherscan, &__support-link { color: var(--color-primary-default); margin-top: 24px; @@ -75,20 +70,6 @@ @include H6; } - &__view-on-etherscan { - @include H7; - - transition: opacity 1s ease-in-out; - } - - &__view-on-etherscan--invisible { - opacity: 0; - } - - &__view-on-etherscan--visible { - opacity: 1; - } - &__amount-and-symbol { color: var(--color-text-default); font-weight: bold; diff --git a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/__snapshots__/view-on-ether-scan-link.test.js.snap b/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/__snapshots__/view-on-ether-scan-link.test.js.snap deleted file mode 100644 index 89d31d36d..000000000 --- a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/__snapshots__/view-on-ether-scan-link.test.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ViewOnEtherScanLink renders the component with a custom block explorer link 1`] = ` -
-
- View Swap at custom-blockchain.explorer -
-
-`; - -exports[`ViewOnEtherScanLink renders the component with initial props 1`] = ` -
-
- View Swap on Etherscan -
-
-`; diff --git a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/index.js b/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/index.js deleted file mode 100644 index 6aa4a3347..000000000 --- a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './view-on-ether-scan-link'; diff --git a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js b/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js deleted file mode 100644 index baa13ef02..000000000 --- a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.js +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import { I18nContext } from '../../../../contexts/i18n'; -import { getURLHostName } from '../../../../helpers/utils/util'; -import { MetaMetricsContext } from '../../../../contexts/metametrics'; -import { EVENT } from '../../../../../shared/constants/metametrics'; - -export default function ViewOnEtherScanLink({ - txHash, - blockExplorerUrl, - isCustomBlockExplorerUrl, -}) { - const t = useContext(I18nContext); - const trackEvent = useContext(MetaMetricsContext); - - return ( -
{ - trackEvent({ - event: 'Clicked Block Explorer Link', - category: EVENT.CATEGORIES.SWAPS, - properties: { - link_type: 'Transaction Block Explorer', - action: 'Swap Transaction', - block_explorer_domain: getURLHostName(blockExplorerUrl), - }, - }); - global.platform.openTab({ url: blockExplorerUrl }); - }} - > - {isCustomBlockExplorerUrl - ? t('viewOnCustomBlockExplorer', [ - t('blockExplorerSwapAction'), - getURLHostName(blockExplorerUrl), - ]) - : t('viewOnEtherscan', [t('blockExplorerSwapAction')])} -
- ); -} - -ViewOnEtherScanLink.propTypes = { - txHash: PropTypes.string, - blockExplorerUrl: PropTypes.string, - isCustomBlockExplorerUrl: PropTypes.bool, -}; diff --git a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.test.js b/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.test.js deleted file mode 100644 index 0fc96d87d..000000000 --- a/ui/pages/swaps/awaiting-swap/view-on-ether-scan-link/view-on-ether-scan-link.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import { renderWithProvider } from '../../../../../test/jest'; -import ViewOnEtherScanLink from '.'; - -const createProps = (customProps = {}) => { - return { - txHash: - '0x58e5a0fc7fbc849eddc100d44e86276168a8c7baaa5604e44ba6f5eb8ba1b7eb', - blockExplorerUrl: 'https://block.explorer', - isCustomBlockExplorerUrl: false, - ...customProps, - }; -}; - -describe('ViewOnEtherScanLink', () => { - it('renders the component with initial props', () => { - const { container, getByText } = renderWithProvider( - , - ); - expect(getByText('View Swap on Etherscan')).toBeInTheDocument(); - expect(container).toMatchSnapshot(); - }); - - it('renders the component with a custom block explorer link', () => { - const { container, getByText } = renderWithProvider( - , - ); - expect( - getByText('View Swap at custom-blockchain.explorer'), - ).toBeInTheDocument(); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/ui/pages/swaps/create-new-swap/create-new-swap.js b/ui/pages/swaps/create-new-swap/create-new-swap.js new file mode 100644 index 000000000..273908549 --- /dev/null +++ b/ui/pages/swaps/create-new-swap/create-new-swap.js @@ -0,0 +1,47 @@ +import React, { useContext } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router-dom'; +import isEqual from 'lodash/isEqual'; + +import Box from '../../../components/ui/box'; +import { I18nContext } from '../../../contexts/i18n'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { EVENT } from '../../../../shared/constants/metametrics'; +import { + navigateBackToBuildQuote, + setSwapsFromToken, +} from '../../../ducks/swaps/swaps'; +import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'; +import { getSwapsDefaultToken } from '../../../selectors'; + +export default function CreateNewSwap({ sensitiveTrackingProperties }) { + const t = useContext(I18nContext); + const trackEvent = useContext(MetaMetricsContext); + const dispatch = useDispatch(); + const history = useHistory(); + const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual); + + return ( + + + + ); +} + +CreateNewSwap.propTypes = { + sensitiveTrackingProperties: PropTypes.object.isRequired, +}; diff --git a/ui/pages/swaps/create-new-swap/create-new-swap.test.js b/ui/pages/swaps/create-new-swap/create-new-swap.test.js new file mode 100644 index 000000000..53835efff --- /dev/null +++ b/ui/pages/swaps/create-new-swap/create-new-swap.test.js @@ -0,0 +1,27 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; + +import { + renderWithProvider, + createSwapsMockStore, +} from '../../../../test/jest'; +import CreateNewSwap from '.'; + +const createProps = (customProps = {}) => { + return { + sensitiveProperties: {}, + ...customProps, + }; +}; + +describe('CreateNewSwap', () => { + it('renders the component with initial props', () => { + const store = configureMockStore()(createSwapsMockStore()); + const props = createProps(); + const { getByText } = renderWithProvider( + , + store, + ); + expect(getByText('Create a new swap')).toBeInTheDocument(); + }); +}); diff --git a/ui/pages/swaps/create-new-swap/index.js b/ui/pages/swaps/create-new-swap/index.js new file mode 100644 index 000000000..d9e08ef8b --- /dev/null +++ b/ui/pages/swaps/create-new-swap/index.js @@ -0,0 +1 @@ +export { default } from './create-new-swap'; diff --git a/ui/pages/swaps/create-new-swap/index.scss b/ui/pages/swaps/create-new-swap/index.scss new file mode 100644 index 000000000..fb7beecf5 --- /dev/null +++ b/ui/pages/swaps/create-new-swap/index.scss @@ -0,0 +1,8 @@ +.create-new-swap { + button { + @include H5; + + color: var(--color-primary-default); + background-color: transparent; + } +} diff --git a/ui/pages/swaps/index.scss b/ui/pages/swaps/index.scss index faa2e8b9e..de6e67a64 100644 --- a/ui/pages/swaps/index.scss +++ b/ui/pages/swaps/index.scss @@ -15,6 +15,8 @@ @import 'swaps-footer/index'; @import 'view-quote/index'; @import 'import-token/index'; +@import 'create-new-swap/index'; +@import 'view-on-block-explorer/index'; .swaps { display: flex; diff --git a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js index c11b7b82e..85cc42f25 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js @@ -1,6 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { useHistory } from 'react-router-dom'; +import { getBlockExplorerLink } from '@metamask/etherscan-link'; import { I18nContext } from '../../../contexts/i18n'; import { @@ -22,7 +23,9 @@ import { getUSDConversionRate, conversionRateSelector, getCurrentCurrency, + getRpcPrefsForCurrentProvider, } from '../../../selectors'; +import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps'; import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import { DEFAULT_ROUTE, @@ -54,6 +57,8 @@ import { getFeeForSmartTransaction, } from '../swaps.util'; import { MetaMetricsContext } from '../../../contexts/metametrics'; +import CreateNewSwap from '../create-new-swap'; +import ViewOnBlockExplorer from '../view-on-block-explorer'; import SuccessIcon from './success-icon'; import RevertedIcon from './reverted-icon'; import CanceledIcon from './canceled-icon'; @@ -79,12 +84,17 @@ export default function SmartTransactionStatus() { const smartTransactionsOptInStatus = useSelector( getSmartTransactionsOptInStatus, ); + const chainId = useSelector(getCurrentChainId); + const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual); const swapsNetworkConfig = useSelector(getSwapsNetworkConfig); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); const currentSmartTransactionsEnabled = useSelector( getCurrentSmartTransactionsEnabled, ); - const chainId = useSelector(getCurrentChainId); + const baseNetworkUrl = + rpcPrefs.blockExplorerUrl ?? + SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ?? + null; const nativeCurrencySymbol = useSelector(getNativeCurrency); const conversionRate = useSelector(conversionRateSelector); const USDConversionRate = useSelector(getUSDConversionRate); @@ -139,6 +149,7 @@ export default function SmartTransactionStatus() { const showCloseButtonOnly = isSmartTransactionPending || smartTransactionStatus === SMART_TRANSACTION_STATUSES.SUCCESS; + const txHash = latestSmartTransaction?.statusMetadata?.minedHash; useEffect(() => { trackEvent({ @@ -190,6 +201,7 @@ export default function SmartTransactionStatus() { let description; let subDescription; let icon; + let blockExplorerUrl; if (isSmartTransactionPending) { if (cancelSwapLinkClicked) { headerText = t('stxTryingToCancel'); @@ -238,6 +250,12 @@ export default function SmartTransactionStatus() { ]); icon = ; } + if (txHash && latestSmartTransactionUuid) { + blockExplorerUrl = getBlockExplorerLink( + { hash: txHash, chainId }, + { blockExplorerUrl: baseNetworkUrl }, + ); + } const showCancelSwapLink = latestSmartTransaction.cancellable && !cancelSwapLinkClicked; @@ -397,12 +415,18 @@ export default function SmartTransactionStatus() { {description && ( {description} )} + {blockExplorerUrl && ( + + )} } + {smartTransactionStatus === SMART_TRANSACTION_STATUSES.SUCCESS ? ( + + ) : null} { if (showCloseButtonOnly) { diff --git a/ui/pages/swaps/view-on-block-explorer/__snapshots__/view-on-block-explorer.test.js.snap b/ui/pages/swaps/view-on-block-explorer/__snapshots__/view-on-block-explorer.test.js.snap new file mode 100644 index 000000000..e7b00963c --- /dev/null +++ b/ui/pages/swaps/view-on-block-explorer/__snapshots__/view-on-block-explorer.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ViewOnBlockExplorer renders the component with initial props 1`] = ` +
+
+ +
+
+`; diff --git a/ui/pages/swaps/view-on-block-explorer/index.js b/ui/pages/swaps/view-on-block-explorer/index.js new file mode 100644 index 000000000..7f7e427fc --- /dev/null +++ b/ui/pages/swaps/view-on-block-explorer/index.js @@ -0,0 +1 @@ +export { default } from './view-on-block-explorer'; diff --git a/ui/pages/swaps/view-on-block-explorer/index.scss b/ui/pages/swaps/view-on-block-explorer/index.scss new file mode 100644 index 000000000..ed05e956c --- /dev/null +++ b/ui/pages/swaps/view-on-block-explorer/index.scss @@ -0,0 +1,8 @@ +.view-on-block-explorer { + button { + @include H7; + + color: var(--color-primary-default); + background-color: transparent; + } +} diff --git a/ui/pages/swaps/view-on-block-explorer/view-on-block-explorer.js b/ui/pages/swaps/view-on-block-explorer/view-on-block-explorer.js new file mode 100644 index 000000000..145ca010f --- /dev/null +++ b/ui/pages/swaps/view-on-block-explorer/view-on-block-explorer.js @@ -0,0 +1,47 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; + +import Box from '../../../components/ui/box'; +import { I18nContext } from '../../../contexts/i18n'; +import { getURLHostName } from '../../../helpers/utils/util'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { EVENT } from '../../../../shared/constants/metametrics'; + +export default function ViewOnBlockExplorer({ + blockExplorerUrl, + sensitiveTrackingProperties, +}) { + const t = useContext(I18nContext); + const trackEvent = useContext(MetaMetricsContext); + const blockExplorerHostName = getURLHostName(blockExplorerUrl); + + return ( + + + + ); +} + +ViewOnBlockExplorer.propTypes = { + blockExplorerUrl: PropTypes.string.isRequired, + sensitiveTrackingProperties: PropTypes.object.isRequired, +}; diff --git a/ui/pages/swaps/view-on-block-explorer/view-on-block-explorer.test.js b/ui/pages/swaps/view-on-block-explorer/view-on-block-explorer.test.js new file mode 100644 index 000000000..e2917a68f --- /dev/null +++ b/ui/pages/swaps/view-on-block-explorer/view-on-block-explorer.test.js @@ -0,0 +1,23 @@ +import React from 'react'; + +import { renderWithProvider } from '../../../../test/jest'; +import ViewOnBlockExplorer from '.'; + +const createProps = (customProps = {}) => { + return { + txHash: + '0x58e5a0fc7fbc849eddc100d44e86276168a8c7baaa5604e44ba6f5eb8ba1b7eb', + blockExplorerUrl: 'https://etherscan.io', + ...customProps, + }; +}; + +describe('ViewOnBlockExplorer', () => { + it('renders the component with initial props', () => { + const { container, getByText } = renderWithProvider( + , + ); + expect(getByText('View Swap at etherscan.io')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); +});