mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
[MMI] Added code fencing in transaction list (#18071)
* Added code fencing in transaction list * Fixed import * Fixed tests * Fixed indentation * Fixed code fences * Removed custody icon in favor of svg * Fix prettier * lint * Fixed prettier issue * adds check before set state with variable _mounted * lint * check for address in selectedIdentity * review fix * lint * updates test * lint * clean up * prettier * adds missing locale * Added tests and improved code * Fixed code --------- Co-authored-by: Antonio Regadas <antonio.regadas@consensys.net>
This commit is contained in:
parent
832ce634fd
commit
00bad7b8a8
3
app/_locales/en/messages.json
generated
3
app/_locales/en/messages.json
generated
@ -4933,6 +4933,9 @@
|
|||||||
"viewOnOpensea": {
|
"viewOnOpensea": {
|
||||||
"message": "View on Opensea"
|
"message": "View on Opensea"
|
||||||
},
|
},
|
||||||
|
"viewinCustodianApp": {
|
||||||
|
"message": "View in custodian app"
|
||||||
|
},
|
||||||
"viewinExplorer": {
|
"viewinExplorer": {
|
||||||
"message": "View $1 in explorer",
|
"message": "View $1 in explorer",
|
||||||
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
|
"description": "$1 is the action type. e.g (Account, Transaction, Swap)"
|
||||||
|
@ -11,6 +11,11 @@ import Button from '../../ui/button';
|
|||||||
import Tooltip from '../../ui/tooltip';
|
import Tooltip from '../../ui/tooltip';
|
||||||
import CancelButton from '../cancel-button';
|
import CancelButton from '../cancel-button';
|
||||||
import Popover from '../../ui/popover';
|
import Popover from '../../ui/popover';
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
import Box from '../../ui/box/box';
|
||||||
|
import { Icon, IconName, Text } from '../../component-library';
|
||||||
|
import { IconColor } from '../../../helpers/constants/design-system';
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
import { SECOND } from '../../../../shared/constants/time';
|
import { SECOND } from '../../../../shared/constants/time';
|
||||||
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
||||||
import { TransactionType } from '../../../../shared/constants/transaction';
|
import { TransactionType } from '../../../../shared/constants/transaction';
|
||||||
@ -52,10 +57,18 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
isCustomNetwork: PropTypes.bool,
|
isCustomNetwork: PropTypes.bool,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
blockExplorerLinkText: PropTypes.object,
|
blockExplorerLinkText: PropTypes.object,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
getCustodianTransactionDeepLink: PropTypes.func,
|
||||||
|
selectedIdentity: PropTypes.object,
|
||||||
|
transactionNote: PropTypes.string,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
justCopied: false,
|
justCopied: false,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
custodyTransactionDeepLink: null,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBlockExplorerClick = () => {
|
handleBlockExplorerClick = () => {
|
||||||
@ -124,16 +137,57 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { recipientAddress, tryReverseResolveAddress } = this.props;
|
const {
|
||||||
|
recipientAddress,
|
||||||
|
tryReverseResolveAddress,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
selectedIdentity,
|
||||||
|
transactionGroup,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
this._mounted = true;
|
||||||
|
const address = selectedIdentity?.address;
|
||||||
|
const custodyId = transactionGroup?.primaryTransaction?.custodyId;
|
||||||
|
|
||||||
|
if (this._mounted && address && custodyId) {
|
||||||
|
this.getCustodianTransactionDeepLink(address, custodyId);
|
||||||
|
}
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
if (recipientAddress) {
|
if (recipientAddress) {
|
||||||
tryReverseResolveAddress(recipientAddress);
|
tryReverseResolveAddress(recipientAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
getCustodianTransactionDeepLink = async (address, custodyId) => {
|
||||||
|
const { getCustodianTransactionDeepLink } = this.props;
|
||||||
|
|
||||||
|
const custodyTransactionDeepLink = await getCustodianTransactionDeepLink(
|
||||||
|
address,
|
||||||
|
custodyId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (custodyTransactionDeepLink && this._mounted) {
|
||||||
|
this.setState({ custodyTransactionDeepLink });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._mounted = false;
|
||||||
|
}
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const { justCopied } = this.state;
|
const {
|
||||||
|
justCopied,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
custodyTransactionDeepLink,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
transactionGroup,
|
transactionGroup,
|
||||||
primaryCurrency,
|
primaryCurrency,
|
||||||
@ -152,6 +206,9 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
showCancel,
|
showCancel,
|
||||||
transactionStatus: TransactionStatus,
|
transactionStatus: TransactionStatus,
|
||||||
blockExplorerLinkText,
|
blockExplorerLinkText,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
transactionNote,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
primaryTransaction: transaction,
|
primaryTransaction: transaction,
|
||||||
@ -229,6 +286,30 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
custodyTransactionDeepLink &&
|
||||||
|
custodyTransactionDeepLink.url && (
|
||||||
|
<Tooltip
|
||||||
|
wrapperClassName="transaction-list-item-details__header-button"
|
||||||
|
containerClassName="transaction-list-item-details__header-button-tooltip-container"
|
||||||
|
title={t('viewinCustodianApp')}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="raised"
|
||||||
|
onClick={() => {
|
||||||
|
window.open(custodyTransactionDeepLink.url);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={IconName.Custody}
|
||||||
|
color={IconColor.primaryDefault}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="transaction-list-item-details__body">
|
<div className="transaction-list-item-details__body">
|
||||||
@ -281,6 +362,20 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
primaryCurrency={primaryCurrency}
|
primaryCurrency={primaryCurrency}
|
||||||
className="transaction-list-item-details__transaction-breakdown"
|
className="transaction-list-item-details__transaction-breakdown"
|
||||||
/>
|
/>
|
||||||
|
{
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
transactionNote && transactionNote.length !== 0 && (
|
||||||
|
<Box className="transaction-list-item-details__transaction-breakdown">
|
||||||
|
<Text as="h4" className="transaction-breakdown__title">
|
||||||
|
{t('transactionNote')}
|
||||||
|
</Text>
|
||||||
|
<Text as="p" className="transaction-breakdown__description">
|
||||||
|
{transactionNote}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
}
|
||||||
{transactionGroup.initialTransaction.type !==
|
{transactionGroup.initialTransaction.type !==
|
||||||
TransactionType.incoming && (
|
TransactionType.incoming && (
|
||||||
<Disclosure title={t('activityLog')} size="small">
|
<Disclosure title={t('activityLog')} size="small">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import configureMockStore from 'redux-mock-store';
|
import configureMockStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
import { TransactionStatus } from '../../../../shared/constants/transaction';
|
import { TransactionStatus } from '../../../../shared/constants/transaction';
|
||||||
import { GAS_LIMITS } from '../../../../shared/constants/gas';
|
import { GAS_LIMITS } from '../../../../shared/constants/gas';
|
||||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||||
@ -13,6 +14,14 @@ jest.mock('../../../store/actions.ts', () => ({
|
|||||||
addPollingTokenToAppState: jest.fn(),
|
addPollingTokenToAppState: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let mockGetCustodianTransactionDeepLink = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('../../../store/institutional/institution-background', () => ({
|
||||||
|
mmiActionsFactory: () => ({
|
||||||
|
getCustodianTransactionDeepLink: () => mockGetCustodianTransactionDeepLink,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('TransactionListItemDetails Component', () => {
|
describe('TransactionListItemDetails Component', () => {
|
||||||
const transaction = {
|
const transaction = {
|
||||||
history: [],
|
history: [],
|
||||||
@ -26,6 +35,10 @@ describe('TransactionListItemDetails Component', () => {
|
|||||||
to: '0x2',
|
to: '0x2',
|
||||||
value: '0x2386f26fc10000',
|
value: '0x2386f26fc10000',
|
||||||
},
|
},
|
||||||
|
metadata: {
|
||||||
|
note: 'some note',
|
||||||
|
},
|
||||||
|
custodyId: '1',
|
||||||
};
|
};
|
||||||
|
|
||||||
const transactionGroup = {
|
const transactionGroup = {
|
||||||
@ -58,7 +71,7 @@ describe('TransactionListItemDetails Component', () => {
|
|||||||
rpcPrefs,
|
rpcPrefs,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should render title with title prop', () => {
|
it('should render title with title prop', async () => {
|
||||||
const mockStore = configureMockStore([thunk])(mockState);
|
const mockStore = configureMockStore([thunk])(mockState);
|
||||||
|
|
||||||
const { queryByText } = renderWithProvider(
|
const { queryByText } = renderWithProvider(
|
||||||
@ -66,7 +79,9 @@ describe('TransactionListItemDetails Component', () => {
|
|||||||
mockStore,
|
mockStore,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryByText(props.title)).toBeInTheDocument();
|
await waitFor(() => {
|
||||||
|
expect(queryByText(props.title)).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Retry button', () => {
|
describe('Retry button', () => {
|
||||||
@ -122,4 +137,55 @@ describe('TransactionListItemDetails Component', () => {
|
|||||||
expect(queryByTestId('speedup-button')).toBeInTheDocument();
|
expect(queryByTestId('speedup-button')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Institutional', () => {
|
||||||
|
it('should render correctly if custodyTransactionDeepLink has a url', async () => {
|
||||||
|
mockGetCustodianTransactionDeepLink = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ url: 'https://url.com' });
|
||||||
|
|
||||||
|
const mockStore = configureMockStore([thunk])(mockState);
|
||||||
|
|
||||||
|
renderWithProvider(<TransactionListItemDetails {...props} />, mockStore);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const custodianViewButton = document.querySelector(
|
||||||
|
'[data-original-title="View in custodian app"]',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert that the custodian view button is rendered
|
||||||
|
expect(custodianViewButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly if transactionNote is provided', async () => {
|
||||||
|
const newTransaction = {
|
||||||
|
...transaction,
|
||||||
|
metadata: {
|
||||||
|
note: 'some note',
|
||||||
|
},
|
||||||
|
custodyId: '1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newTransactionGroup = {
|
||||||
|
...transactionGroup,
|
||||||
|
transactions: [newTransaction],
|
||||||
|
primaryTransaction: newTransaction,
|
||||||
|
initialTransaction: newTransaction,
|
||||||
|
};
|
||||||
|
const mockStore = configureMockStore([thunk])(mockState);
|
||||||
|
|
||||||
|
const { queryByText } = renderWithProvider(
|
||||||
|
<TransactionListItemDetails
|
||||||
|
{...props}
|
||||||
|
transactionGroup={newTransactionGroup}
|
||||||
|
/>,
|
||||||
|
mockStore,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(queryByText('some note')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,9 @@ import { connect } from 'react-redux';
|
|||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { tryReverseResolveAddress } from '../../../store/actions';
|
import { tryReverseResolveAddress } from '../../../store/actions';
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
import {
|
import {
|
||||||
getAddressBook,
|
getAddressBook,
|
||||||
getBlockExplorerLinkText,
|
getBlockExplorerLinkText,
|
||||||
@ -11,6 +14,10 @@ import {
|
|||||||
getAccountName,
|
getAccountName,
|
||||||
getMetadataContractName,
|
getMetadataContractName,
|
||||||
getMetaMaskIdentities,
|
getMetaMaskIdentities,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
getSelectedIdentity,
|
||||||
|
getKnownMethodData,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
} from '../../../selectors';
|
} from '../../../selectors';
|
||||||
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
|
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
|
||||||
import TransactionListItemDetails from './transaction-list-item-details.component';
|
import TransactionListItemDetails from './transaction-list-item-details.component';
|
||||||
@ -40,6 +47,13 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const isCustomNetwork = getIsCustomNetwork(state);
|
const isCustomNetwork = getIsCustomNetwork(state);
|
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
const data = ownProps.transactionGroup?.primaryTransaction?.txParams?.data;
|
||||||
|
const methodData = getKnownMethodData(state, data) || {};
|
||||||
|
const transactionNote =
|
||||||
|
ownProps.transactionGroup?.primaryTransaction?.metadata?.note;
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rpcPrefs,
|
rpcPrefs,
|
||||||
recipientEns,
|
recipientEns,
|
||||||
@ -49,14 +63,29 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
blockExplorerLinkText: getBlockExplorerLinkText(state),
|
blockExplorerLinkText: getBlockExplorerLinkText(state),
|
||||||
recipientName,
|
recipientName,
|
||||||
recipientMetadataName,
|
recipientMetadataName,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
methodData,
|
||||||
|
transactionNote,
|
||||||
|
selectedIdentity: getSelectedIdentity(state),
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
const mmiActions = mmiActionsFactory();
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
return {
|
return {
|
||||||
tryReverseResolveAddress: (address) => {
|
tryReverseResolveAddress: (address) => {
|
||||||
return dispatch(tryReverseResolveAddress(address));
|
return dispatch(tryReverseResolveAddress(address));
|
||||||
},
|
},
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
getCustodianTransactionDeepLink: (address, txId) => {
|
||||||
|
return dispatch(
|
||||||
|
mmiActions.getCustodianTransactionDeepLink(address, txId),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,4 +67,13 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
&__icon-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
left: 18px;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,10 @@ import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes';
|
|||||||
import { useShouldShowSpeedUp } from '../../../hooks/useShouldShowSpeedUp';
|
import { useShouldShowSpeedUp } from '../../../hooks/useShouldShowSpeedUp';
|
||||||
import TransactionStatusLabel from '../transaction-status-label/transaction-status-label';
|
import TransactionStatusLabel from '../transaction-status-label/transaction-status-label';
|
||||||
import TransactionIcon from '../transaction-icon';
|
import TransactionIcon from '../transaction-icon';
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
import { IconColor } from '../../../helpers/constants/design-system';
|
||||||
|
import { Icon, IconName, IconSize } from '../../component-library';
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
||||||
import {
|
import {
|
||||||
TransactionGroupCategory,
|
TransactionGroupCategory,
|
||||||
@ -125,6 +129,9 @@ function TransactionListItemInner({
|
|||||||
const isApproval = category === TransactionGroupCategory.approval;
|
const isApproval = category === TransactionGroupCategory.approval;
|
||||||
const isUnapproved = status === TransactionStatus.unapproved;
|
const isUnapproved = status === TransactionStatus.unapproved;
|
||||||
const isSwap = category === TransactionGroupCategory.swap;
|
const isSwap = category === TransactionGroupCategory.swap;
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
const isCustodian = Boolean(transactionGroup.primaryTransaction.custodyId);
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
const className = classnames('transaction-list-item', {
|
const className = classnames('transaction-list-item', {
|
||||||
'transaction-list-item--unconfirmed':
|
'transaction-list-item--unconfirmed':
|
||||||
@ -144,10 +151,29 @@ function TransactionListItemInner({
|
|||||||
setShowDetails((prev) => !prev);
|
setShowDetails((prev) => !prev);
|
||||||
}, [isUnapproved, history, id]);
|
}, [isUnapproved, history, id]);
|
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
const debugTransactionMeta = {
|
||||||
|
'data-hash': transactionGroup.primaryTransaction.hash,
|
||||||
|
...(isCustodian
|
||||||
|
? {
|
||||||
|
'data-custodiantransactionid':
|
||||||
|
transactionGroup.primaryTransaction.custodyId,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
const speedUpButton = useMemo(() => {
|
const speedUpButton = useMemo(() => {
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
if (isCustodian) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
if (!shouldShowSpeedUp || !isPending || isUnapproved) {
|
if (!shouldShowSpeedUp || !isPending || isUnapproved) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -165,10 +191,32 @@ function TransactionListItemInner({
|
|||||||
hasCancelled,
|
hasCancelled,
|
||||||
retryTransaction,
|
retryTransaction,
|
||||||
cancelTransaction,
|
cancelTransaction,
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
isCustodian,
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const showCancelButton = !hasCancelled && isPending && !isUnapproved;
|
|
||||||
const showBorder = process.env.MULTICHAIN;
|
const showBorder = process.env.MULTICHAIN;
|
||||||
|
let showCancelButton = !hasCancelled && isPending && !isUnapproved;
|
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
showCancelButton = showCancelButton && !isCustodian;
|
||||||
|
const PENDING_COLOR = IconColor.iconAlternative;
|
||||||
|
const OK_COLOR = IconColor.primaryDefault;
|
||||||
|
const FAIL_COLOR = IconColor.errorDefault;
|
||||||
|
const getTransactionColor = (tsStatus) => {
|
||||||
|
switch (tsStatus) {
|
||||||
|
case TransactionStatus.signed:
|
||||||
|
return PENDING_COLOR;
|
||||||
|
case TransactionStatus.rejected:
|
||||||
|
case TransactionStatus.failed:
|
||||||
|
case TransactionStatus.dropped:
|
||||||
|
return FAIL_COLOR;
|
||||||
|
default:
|
||||||
|
return OK_COLOR;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -177,7 +225,26 @@ function TransactionListItemInner({
|
|||||||
className={className}
|
className={className}
|
||||||
title={title}
|
title={title}
|
||||||
icon={
|
icon={
|
||||||
<TransactionIcon category={category} status={displayedStatusKey} />
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
isCustodian ? (
|
||||||
|
<div style={{ position: 'relative' }} data-testid="custody-icon">
|
||||||
|
<TransactionIcon
|
||||||
|
category={category}
|
||||||
|
status={displayedStatusKey}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name={IconName.Custody}
|
||||||
|
className="transaction-list-item__icon-badge"
|
||||||
|
color={getTransactionColor(status)}
|
||||||
|
size={IconSize.Xs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
<TransactionIcon category={category} status={displayedStatusKey} />
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
)
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
}
|
}
|
||||||
showBorder={showBorder}
|
showBorder={showBorder}
|
||||||
subtitle={
|
subtitle={
|
||||||
@ -188,6 +255,12 @@ function TransactionListItemInner({
|
|||||||
error={err}
|
error={err}
|
||||||
date={date}
|
date={date}
|
||||||
status={displayedStatusKey}
|
status={displayedStatusKey}
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
custodyStatus={transactionGroup.primaryTransaction.custodyStatus}
|
||||||
|
custodyStatusDisplayText={
|
||||||
|
transactionGroup.primaryTransaction.custodyStatusDisplayText
|
||||||
|
}
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
/>
|
/>
|
||||||
{subtitleContainsOrigin ? (
|
{subtitleContainsOrigin ? (
|
||||||
<SiteOrigin siteOrigin={subtitle} />
|
<SiteOrigin siteOrigin={subtitle} />
|
||||||
@ -224,6 +297,11 @@ function TransactionListItemInner({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
<a {...debugTransactionMeta} className="test-transaction-meta" />
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{showDetails && (
|
{showDetails && (
|
||||||
<TransactionListItemDetails
|
<TransactionListItemDetails
|
||||||
@ -234,11 +312,28 @@ function TransactionListItemInner({
|
|||||||
senderAddress={senderAddress}
|
senderAddress={senderAddress}
|
||||||
recipientAddress={recipientAddress}
|
recipientAddress={recipientAddress}
|
||||||
onRetry={retryTransaction}
|
onRetry={retryTransaction}
|
||||||
showRetry={status === TransactionStatus.failed && !isSwap}
|
showRetry={
|
||||||
showSpeedUp={shouldShowSpeedUp}
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
!isCustodian &&
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
status === TransactionStatus.failed &&
|
||||||
|
!isSwap
|
||||||
|
}
|
||||||
|
showSpeedUp={
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
!isCustodian &&
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
shouldShowSpeedUp
|
||||||
|
}
|
||||||
isEarliestNonce={isEarliestNonce}
|
isEarliestNonce={isEarliestNonce}
|
||||||
onCancel={cancelTransaction}
|
onCancel={cancelTransaction}
|
||||||
showCancel={isPending && !hasCancelled}
|
showCancel={
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
!isCustodian &&
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
isPending &&
|
||||||
|
!hasCancelled
|
||||||
|
}
|
||||||
transactionStatus={() => (
|
transactionStatus={() => (
|
||||||
<TransactionStatusLabel
|
<TransactionStatusLabel
|
||||||
isPending={isPending}
|
isPending={isPending}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { fireEvent } from '@testing-library/react';
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import mockState from '../../../../test/data/mock-state.json';
|
||||||
import transactionGroup from '../../../../test/data/mock-pending-transaction-data.json';
|
import transactionGroup from '../../../../test/data/mock-pending-transaction-data.json';
|
||||||
import {
|
import {
|
||||||
getConversionRate,
|
getConversionRate,
|
||||||
@ -72,6 +74,20 @@ jest.mock('react', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('../../../store/actions.ts', () => ({
|
||||||
|
tryReverseResolveAddress: jest.fn().mockReturnValue({ type: 'TYPE' }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../store/institutional/institution-background', () => ({
|
||||||
|
mmiActionsFactory: () => ({
|
||||||
|
getCustodianTransactionDeepLink: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ type: 'TYPE' }),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockStore = configureStore();
|
||||||
|
|
||||||
const generateUseSelectorRouter = (opts) => (selector) => {
|
const generateUseSelectorRouter = (opts) => (selector) => {
|
||||||
if (selector === getConversionRate) {
|
if (selector === getConversionRate) {
|
||||||
return 1;
|
return 1;
|
||||||
@ -146,5 +162,49 @@ describe('TransactionListItem', () => {
|
|||||||
fireEvent.click(cancelButton);
|
fireEvent.click(cancelButton);
|
||||||
expect(getByText('Cancel transaction')).toBeInTheDocument();
|
expect(getByText('Cancel transaction')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have a custodian Tx and show the custody icon', () => {
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({
|
||||||
|
balance: '2AA1EFB94E0000',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const newTransactionGroup = {
|
||||||
|
...transactionGroup,
|
||||||
|
...(transactionGroup.primaryTransaction.custodyId = '1'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { queryByTestId } = renderWithProvider(
|
||||||
|
<TransactionListItem transactionGroup={newTransactionGroup} />,
|
||||||
|
);
|
||||||
|
expect(queryByTestId('custody-icon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should click the custody list item and view the send screen', () => {
|
||||||
|
const store = mockStore(mockState);
|
||||||
|
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({
|
||||||
|
balance: '2AA1EFB94E0000',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const newTransactionGroup = {
|
||||||
|
...transactionGroup,
|
||||||
|
...(transactionGroup.primaryTransaction.custodyId = '1'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { queryByTestId } = renderWithProvider(
|
||||||
|
<TransactionListItem transactionGroup={newTransactionGroup} />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
|
||||||
|
const custodyListItem = queryByTestId('custody-icon');
|
||||||
|
fireEvent.click(custodyListItem);
|
||||||
|
|
||||||
|
const sendTextExists = screen.queryAllByText('Send');
|
||||||
|
expect(sendTextExists).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -123,23 +123,36 @@ export default function TransactionList({
|
|||||||
<div className="transaction-list__header">
|
<div className="transaction-list__header">
|
||||||
{`${t('queue')} (${pendingTransactions.length})`}
|
{`${t('queue')} (${pendingTransactions.length})`}
|
||||||
</div>
|
</div>
|
||||||
{pendingTransactions.map((transactionGroup, index) =>
|
{pendingTransactions
|
||||||
transactionGroup.initialTransaction.transactionType ===
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
TransactionType.smart ? (
|
.sort(
|
||||||
<SmartTransactionListItem
|
(a, b) => b.primaryTransaction.time - a.primaryTransaction.time,
|
||||||
isEarliestNonce={index === 0}
|
)
|
||||||
smartTransaction={transactionGroup.initialTransaction}
|
///: END:ONLY_INCLUDE_IN
|
||||||
transactionGroup={transactionGroup}
|
.map((transactionGroup, index) => {
|
||||||
key={`${transactionGroup.nonce}:${index}`}
|
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
|
||||||
/>
|
if (
|
||||||
) : (
|
transactionGroup.initialTransaction.transactionType ===
|
||||||
<TransactionListItem
|
TransactionType.smart
|
||||||
isEarliestNonce={index === 0}
|
) {
|
||||||
transactionGroup={transactionGroup}
|
return (
|
||||||
key={`${transactionGroup.nonce}:${index}`}
|
<SmartTransactionListItem
|
||||||
/>
|
isEarliestNonce={index === 0}
|
||||||
),
|
smartTransaction={transactionGroup.initialTransaction}
|
||||||
)}
|
transactionGroup={transactionGroup}
|
||||||
|
key={`${transactionGroup.nonce}:${index}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
return (
|
||||||
|
<TransactionListItem
|
||||||
|
isEarliestNonce={index === 0}
|
||||||
|
transactionGroup={transactionGroup}
|
||||||
|
key={`${transactionGroup.nonce}:${index}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="transaction-list__completed-transactions">
|
<div className="transaction-list__completed-transactions">
|
||||||
@ -148,6 +161,11 @@ export default function TransactionList({
|
|||||||
) : null}
|
) : null}
|
||||||
{completedTransactions.length > 0 ? (
|
{completedTransactions.length > 0 ? (
|
||||||
completedTransactions
|
completedTransactions
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||||
|
.sort(
|
||||||
|
(a, b) => b.primaryTransaction.time - a.primaryTransaction.time,
|
||||||
|
)
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
.slice(0, limit)
|
.slice(0, limit)
|
||||||
.map((transactionGroup, index) =>
|
.map((transactionGroup, index) =>
|
||||||
transactionGroup.initialTransaction?.transactionType ===
|
transactionGroup.initialTransaction?.transactionType ===
|
||||||
|
Loading…
Reference in New Issue
Block a user