mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
enable direct routing to the send page (#15259)
This commit is contained in:
parent
d92936475a
commit
751c119d10
@ -1081,8 +1081,10 @@ const slice = createSlice({
|
|||||||
updateGasLimit: (state, action) => {
|
updateGasLimit: (state, action) => {
|
||||||
const draftTransaction =
|
const draftTransaction =
|
||||||
state.draftTransactions[state.currentTransactionUUID];
|
state.draftTransactions[state.currentTransactionUUID];
|
||||||
draftTransaction.gas.gasLimit = addHexPrefix(action.payload);
|
if (draftTransaction) {
|
||||||
slice.caseReducers.calculateGasTotal(state);
|
draftTransaction.gas.gasLimit = addHexPrefix(action.payload);
|
||||||
|
slice.caseReducers.calculateGasTotal(state);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* sets the layer 1 fees total (for a multi-layer fee network)
|
* sets the layer 1 fees total (for a multi-layer fee network)
|
||||||
@ -2334,6 +2336,19 @@ export function getCurrentDraftTransaction(state) {
|
|||||||
return state[name].draftTransactions[getCurrentTransactionUUID(state)] ?? {};
|
return state[name].draftTransactions[getCurrentTransactionUUID(state)] ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector that returns true if a draft transaction exists.
|
||||||
|
*
|
||||||
|
* @type {Selector<boolean>}
|
||||||
|
*/
|
||||||
|
export function getDraftTransactionExists(state) {
|
||||||
|
const draftTransaction = getCurrentDraftTransaction(state);
|
||||||
|
if (Object.keys(draftTransaction).length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Gas selectors
|
// Gas selectors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,6 +5,7 @@ import PageContainerHeader from '../../../components/ui/page-container/page-cont
|
|||||||
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import {
|
import {
|
||||||
|
getDraftTransactionExists,
|
||||||
getSendAsset,
|
getSendAsset,
|
||||||
getSendStage,
|
getSendStage,
|
||||||
resetSendState,
|
resetSendState,
|
||||||
@ -19,15 +20,18 @@ export default function SendHeader() {
|
|||||||
const stage = useSelector(getSendStage);
|
const stage = useSelector(getSendStage);
|
||||||
const asset = useSelector(getSendAsset);
|
const asset = useSelector(getSendAsset);
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
|
const draftTransactionExists = useSelector(getDraftTransactionExists);
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dispatch(resetSendState());
|
dispatch(resetSendState());
|
||||||
history.push(mostRecentOverviewPage);
|
history.push(mostRecentOverviewPage);
|
||||||
};
|
};
|
||||||
|
|
||||||
let title = asset.type === ASSET_TYPES.NATIVE ? t('send') : t('sendTokens');
|
let title = asset?.type === ASSET_TYPES.NATIVE ? t('send') : t('sendTokens');
|
||||||
|
|
||||||
if (stage === SEND_STAGES.ADD_RECIPIENT || stage === SEND_STAGES.INACTIVE) {
|
if (
|
||||||
|
draftTransactionExists === false ||
|
||||||
|
[SEND_STAGES.ADD_RECIPIENT, SEND_STAGES.INACTIVE].includes(stage)
|
||||||
|
) {
|
||||||
title = t('sendTo');
|
title = t('sendTo');
|
||||||
} else if (stage === SEND_STAGES.EDIT) {
|
} else if (stage === SEND_STAGES.EDIT) {
|
||||||
title = t('edit');
|
title = t('edit');
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useCallback, useContext } from 'react';
|
import React, { useEffect, useCallback, useContext, useRef } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
addHistoryEntry,
|
addHistoryEntry,
|
||||||
|
getDraftTransactionExists,
|
||||||
getIsUsingMyAccountForRecipientSearch,
|
getIsUsingMyAccountForRecipientSearch,
|
||||||
getRecipient,
|
getRecipient,
|
||||||
getRecipientUserInput,
|
getRecipientUserInput,
|
||||||
@ -10,6 +11,7 @@ import {
|
|||||||
resetRecipientInput,
|
resetRecipientInput,
|
||||||
resetSendState,
|
resetSendState,
|
||||||
SEND_STAGES,
|
SEND_STAGES,
|
||||||
|
startNewDraftTransaction,
|
||||||
updateRecipient,
|
updateRecipient,
|
||||||
updateRecipientUserInput,
|
updateRecipientUserInput,
|
||||||
} from '../../ducks/send';
|
} from '../../ducks/send';
|
||||||
@ -18,6 +20,7 @@ import { getSendHexDataFeatureFlagState } from '../../ducks/metamask/metamask';
|
|||||||
import { showQrScanner } from '../../store/actions';
|
import { showQrScanner } from '../../store/actions';
|
||||||
import { MetaMetricsContext } from '../../contexts/metametrics';
|
import { MetaMetricsContext } from '../../contexts/metametrics';
|
||||||
import { EVENT } from '../../../shared/constants/metametrics';
|
import { EVENT } from '../../../shared/constants/metametrics';
|
||||||
|
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
||||||
import SendHeader from './send-header';
|
import SendHeader from './send-header';
|
||||||
import AddRecipient from './send-content/add-recipient';
|
import AddRecipient from './send-content/add-recipient';
|
||||||
import SendContent from './send-content';
|
import SendContent from './send-content';
|
||||||
@ -29,6 +32,7 @@ const sendSliceIsCustomPriceExcessive = (state) =>
|
|||||||
|
|
||||||
export default function SendTransactionScreen() {
|
export default function SendTransactionScreen() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const startedNewDraftTransaction = useRef(false);
|
||||||
const stage = useSelector(getSendStage);
|
const stage = useSelector(getSendStage);
|
||||||
const gasIsExcessive = useSelector(sendSliceIsCustomPriceExcessive);
|
const gasIsExcessive = useSelector(sendSliceIsCustomPriceExcessive);
|
||||||
const isUsingMyAccountsForRecipientSearch = useSelector(
|
const isUsingMyAccountsForRecipientSearch = useSelector(
|
||||||
@ -37,6 +41,7 @@ export default function SendTransactionScreen() {
|
|||||||
const recipient = useSelector(getRecipient);
|
const recipient = useSelector(getRecipient);
|
||||||
const showHexData = useSelector(getSendHexDataFeatureFlagState);
|
const showHexData = useSelector(getSendHexDataFeatureFlagState);
|
||||||
const userInput = useSelector(getRecipientUserInput);
|
const userInput = useSelector(getRecipientUserInput);
|
||||||
|
const draftTransactionExists = useSelector(getDraftTransactionExists);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const trackEvent = useContext(MetaMetricsContext);
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
|
||||||
@ -46,6 +51,23 @@ export default function SendTransactionScreen() {
|
|||||||
dispatch(resetSendState());
|
dispatch(resetSendState());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is possible to route to this page directly, either by typing in the url
|
||||||
|
* or by clicking the browser back button after progressing to the confirm
|
||||||
|
* screen. In the case where a draft transaction does not yet exist, this
|
||||||
|
* hook is responsible for creating it. We will assume that this is a native
|
||||||
|
* asset send.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
draftTransactionExists === false &&
|
||||||
|
startedNewDraftTransaction.current === false
|
||||||
|
) {
|
||||||
|
startedNewDraftTransaction.current = true;
|
||||||
|
dispatch(startNewDraftTransaction({ type: ASSET_TYPES.NATIVE }));
|
||||||
|
}
|
||||||
|
}, [draftTransactionExists, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('beforeunload', cleanup);
|
window.addEventListener('beforeunload', cleanup);
|
||||||
}, [cleanup]);
|
}, [cleanup]);
|
||||||
@ -70,7 +92,10 @@ export default function SendTransactionScreen() {
|
|||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
if ([SEND_STAGES.EDIT, SEND_STAGES.DRAFT].includes(stage)) {
|
if (
|
||||||
|
draftTransactionExists &&
|
||||||
|
[SEND_STAGES.EDIT, SEND_STAGES.DRAFT].includes(stage)
|
||||||
|
) {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<SendContent
|
<SendContent
|
||||||
|
@ -2,7 +2,7 @@ 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 { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { SEND_STAGES } from '../../ducks/send';
|
import { SEND_STAGES, startNewDraftTransaction } from '../../ducks/send';
|
||||||
import { ensInitialState } from '../../ducks/ens';
|
import { ensInitialState } from '../../ducks/ens';
|
||||||
import { renderWithProvider } from '../../../test/jest';
|
import { renderWithProvider } from '../../../test/jest';
|
||||||
import { RINKEBY_CHAIN_ID } from '../../../shared/constants/network';
|
import { RINKEBY_CHAIN_ID } from '../../../shared/constants/network';
|
||||||
@ -12,6 +12,20 @@ import Send from './send';
|
|||||||
|
|
||||||
const middleware = [thunk];
|
const middleware = [thunk];
|
||||||
|
|
||||||
|
jest.mock('../../ducks/send/send', () => {
|
||||||
|
const original = jest.requireActual('../../ducks/send/send');
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
// We don't really need to start a draft transaction, and the mock store
|
||||||
|
// does not update as a result of action calls so instead we just ensure
|
||||||
|
// that the action WOULD be called.
|
||||||
|
startNewDraftTransaction: jest.fn(() => ({
|
||||||
|
type: 'TEST_START_NEW_DRAFT',
|
||||||
|
payload: null,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => {
|
jest.mock('react-router-dom', () => {
|
||||||
const original = jest.requireActual('react-router-dom');
|
const original = jest.requireActual('react-router-dom');
|
||||||
return {
|
return {
|
||||||
@ -163,6 +177,25 @@ describe('Send Page', () => {
|
|||||||
const { queryByText } = renderWithProvider(<Send />, store);
|
const { queryByText } = renderWithProvider(<Send />, store);
|
||||||
expect(queryByText('Next')).toBeNull();
|
expect(queryByText('Next')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render correctly even when a draftTransaction does not exist', () => {
|
||||||
|
const modifiedStore = {
|
||||||
|
...baseStore,
|
||||||
|
send: {
|
||||||
|
...baseStore.send,
|
||||||
|
currentTransactionUUID: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const store = configureMockStore(middleware)(modifiedStore);
|
||||||
|
const { getByPlaceholderText } = renderWithProvider(<Send />, store);
|
||||||
|
// Ensure that the send flow renders on the add recipient screen when
|
||||||
|
// there is no draft transaction.
|
||||||
|
expect(
|
||||||
|
getByPlaceholderText('Search, public address (0x), or ENS'),
|
||||||
|
).toBeTruthy();
|
||||||
|
// Ensure we start a new draft transaction when its missing.
|
||||||
|
expect(startNewDraftTransaction).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Send and Edit Flow (draft)', () => {
|
describe('Send and Edit Flow (draft)', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user