1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

Show a tx link and the "create a new swap" link for STX (#14995)

This commit is contained in:
Daniel 2022-07-11 17:53:07 +02:00 committed by GitHub
parent 6553b9a29b
commit 8cbfa2ea9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 218 additions and 175 deletions

View File

@ -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 = <SwapFailureIcon />;
content = blockExplorerUrl && (
<ViewOnEtherScanLink
txHash={txHash}
<ViewOnBlockExplorer
blockExplorerUrl={blockExplorerUrl}
isCustomBlockExplorerUrl={isCustomBlockExplorerUrl}
sensitiveTrackingProperties={sensitiveProperties}
/>
);
} else if (errorKey === QUOTES_EXPIRED_ERROR) {
@ -221,10 +211,9 @@ export default function AwaitingSwap({
</span>,
]);
content = blockExplorerUrl && (
<ViewOnEtherScanLink
txHash={txHash}
<ViewOnBlockExplorer
blockExplorerUrl={blockExplorerUrl}
isCustomBlockExplorerUrl={isCustomBlockExplorerUrl}
sensitiveTrackingProperties={sensitiveProperties}
/>
);
} else if (!errorKey && swapComplete) {
@ -240,35 +229,13 @@ export default function AwaitingSwap({
</span>,
]);
content = blockExplorerUrl && (
<ViewOnEtherScanLink
txHash={txHash}
<ViewOnBlockExplorer
blockExplorerUrl={blockExplorerUrl}
isCustomBlockExplorerUrl={isCustomBlockExplorerUrl}
sensitiveTrackingProperties={sensitiveProperties}
/>
);
}
const MakeAnotherSwap = () => {
return (
<Box marginBottom={3}>
<a
href="#"
onClick={async () => {
trackEvent({
event: 'Make Another Swap',
category: EVENT.CATEGORIES.SWAPS,
sensitiveProperties,
});
await dispatch(navigateBackToBuildQuote(history));
dispatch(setSwapsFromToken(defaultSwapsToken));
}}
>
{t('makeAnotherSwap')}
</a>
</Box>
);
};
useEffect(() => {
if (errorKey) {
// If there was an error, stop polling for quotes.
@ -291,7 +258,9 @@ export default function AwaitingSwap({
<div className="awaiting-swap__main-description">{descriptionText}</div>
{content}
</div>
{!errorKey && swapComplete ? <MakeAnotherSwap /> : null}
{!errorKey && swapComplete ? (
<CreateNewSwap sensitiveTrackingProperties={sensitiveProperties} />
) : null}
<SwapsFooter
onSubmit={async () => {
if (errorKey === OFFLINE_FOR_MAINTENANCE) {

View File

@ -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;

View File

@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ViewOnEtherScanLink renders the component with a custom block explorer link 1`] = `
<div>
<div
class="awaiting-swap__view-on-etherscan awaiting-swap__view-on-etherscan--visible"
>
View Swap at custom-blockchain.explorer
</div>
</div>
`;
exports[`ViewOnEtherScanLink renders the component with initial props 1`] = `
<div>
<div
class="awaiting-swap__view-on-etherscan awaiting-swap__view-on-etherscan--visible"
>
View Swap on Etherscan
</div>
</div>
`;

View File

@ -1 +0,0 @@
export { default } from './view-on-ether-scan-link';

View File

@ -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 (
<div
className={classnames('awaiting-swap__view-on-etherscan', {
'awaiting-swap__view-on-etherscan--visible': txHash,
'awaiting-swap__view-on-etherscan--invisible': !txHash,
})}
onClick={() => {
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')])}
</div>
);
}
ViewOnEtherScanLink.propTypes = {
txHash: PropTypes.string,
blockExplorerUrl: PropTypes.string,
isCustomBlockExplorerUrl: PropTypes.bool,
};

View File

@ -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(
<ViewOnEtherScanLink {...createProps()} />,
);
expect(getByText('View Swap on Etherscan')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it('renders the component with a custom block explorer link', () => {
const { container, getByText } = renderWithProvider(
<ViewOnEtherScanLink
{...createProps({
blockExplorerUrl: 'https://custom-blockchain.explorer',
isCustomBlockExplorerUrl: true,
})}
/>,
);
expect(
getByText('View Swap at custom-blockchain.explorer'),
).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
});

View File

@ -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 (
<Box marginBottom={3} className="create-new-swap">
<button
onClick={async () => {
trackEvent({
event: 'Make Another Swap',
category: EVENT.CATEGORIES.SWAPS,
sensitiveProperties: sensitiveTrackingProperties,
});
history.push(DEFAULT_ROUTE); // It cleans up Swaps state.
await dispatch(navigateBackToBuildQuote(history));
dispatch(setSwapsFromToken(defaultSwapsToken));
}}
>
{t('makeAnotherSwap')}
</button>
</Box>
);
}
CreateNewSwap.propTypes = {
sensitiveTrackingProperties: PropTypes.object.isRequired,
};

View File

@ -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(
<CreateNewSwap {...props} />,
store,
);
expect(getByText('Create a new swap')).toBeInTheDocument();
});
});

View File

@ -0,0 +1 @@
export { default } from './create-new-swap';

View File

@ -0,0 +1,8 @@
.create-new-swap {
button {
@include H5;
color: var(--color-primary-default);
background-color: transparent;
}
}

View File

@ -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;

View File

@ -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 = <RevertedIcon />;
}
if (txHash && latestSmartTransactionUuid) {
blockExplorerUrl = getBlockExplorerLink(
{ hash: txHash, chainId },
{ blockExplorerUrl: baseNetworkUrl },
);
}
const showCancelSwapLink =
latestSmartTransaction.cancellable && !cancelSwapLinkClicked;
@ -397,12 +415,18 @@ export default function SmartTransactionStatus() {
{description && (
<Typography
variant={TYPOGRAPHY.H6}
boxProps={{ marginTop: 0 }}
boxProps={{ ...(blockExplorerUrl && { margin: [1, 0, 0] }) }}
color={COLORS.TEXT_ALTERNATIVE}
>
{description}
</Typography>
)}
{blockExplorerUrl && (
<ViewOnBlockExplorer
blockExplorerUrl={blockExplorerUrl}
sensitiveTrackingProperties={sensitiveProperties}
/>
)}
<Box
marginTop={3}
className="smart-transaction-status__background-animation smart-transaction-status__background-animation--bottom"
@ -420,6 +444,9 @@ export default function SmartTransactionStatus() {
{showCancelSwapLink &&
latestSmartTransactionUuid &&
isSmartTransactionPending && <CancelSwap />}
{smartTransactionStatus === SMART_TRANSACTION_STATUSES.SUCCESS ? (
<CreateNewSwap sensitiveTrackingProperties={sensitiveProperties} />
) : null}
<SwapsFooter
onSubmit={async () => {
if (showCloseButtonOnly) {

View File

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ViewOnBlockExplorer renders the component with initial props 1`] = `
<div>
<div
class="box view-on-block-explorer box--margin-top-6 box--flex-direction-row"
>
<button>
View Swap at etherscan.io
</button>
</div>
</div>
`;

View File

@ -0,0 +1 @@
export { default } from './view-on-block-explorer';

View File

@ -0,0 +1,8 @@
.view-on-block-explorer {
button {
@include H7;
color: var(--color-primary-default);
background-color: transparent;
}
}

View File

@ -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 (
<Box marginTop={6} className="view-on-block-explorer">
<button
onClick={() => {
trackEvent({
event: 'Clicked Block Explorer Link',
category: EVENT.CATEGORIES.SWAPS,
sensitiveProperties: sensitiveTrackingProperties,
properties: {
link_type: 'Transaction Block Explorer',
action: 'Swap Transaction',
block_explorer_domain: blockExplorerHostName,
},
});
global.platform.openTab({ url: blockExplorerUrl });
}}
>
{t('viewOnCustomBlockExplorer', [
t('blockExplorerSwapAction'),
blockExplorerHostName,
])}
</button>
</Box>
);
}
ViewOnBlockExplorer.propTypes = {
blockExplorerUrl: PropTypes.string.isRequired,
sensitiveTrackingProperties: PropTypes.object.isRequired,
};

View File

@ -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(
<ViewOnBlockExplorer {...createProps()} />,
);
expect(getByText('View Swap at etherscan.io')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
});