Skip to content

Commit 024a62f

Browse files
brad-deckerdanjm
authored andcommitted
enable direct routing to the send page (#15259)
1 parent 810c00c commit 024a62f

File tree

4 files changed

+85
-8
lines changed

4 files changed

+85
-8
lines changed

ui/ducks/send/send.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,8 +1079,10 @@ const slice = createSlice({
10791079
updateGasLimit: (state, action) => {
10801080
const draftTransaction =
10811081
state.draftTransactions[state.currentTransactionUUID];
1082-
draftTransaction.gas.gasLimit = addHexPrefix(action.payload);
1083-
slice.caseReducers.calculateGasTotal(state);
1082+
if (draftTransaction) {
1083+
draftTransaction.gas.gasLimit = addHexPrefix(action.payload);
1084+
slice.caseReducers.calculateGasTotal(state);
1085+
}
10841086
},
10851087
/**
10861088
* sets the layer 1 fees total (for a multi-layer fee network)
@@ -2346,6 +2348,19 @@ export function getCurrentDraftTransaction(state) {
23462348
return state[name].draftTransactions[getCurrentTransactionUUID(state)] ?? {};
23472349
}
23482350

2351+
/**
2352+
* Selector that returns true if a draft transaction exists.
2353+
*
2354+
* @type {Selector<boolean>}
2355+
*/
2356+
export function getDraftTransactionExists(state) {
2357+
const draftTransaction = getCurrentDraftTransaction(state);
2358+
if (Object.keys(draftTransaction).length === 0) {
2359+
return false;
2360+
}
2361+
return true;
2362+
}
2363+
23492364
// Gas selectors
23502365

23512366
/**

ui/pages/send/send-header/send-header.component.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import PageContainerHeader from '../../../components/ui/page-container/page-cont
55
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
66
import { useI18nContext } from '../../../hooks/useI18nContext';
77
import {
8+
getDraftTransactionExists,
89
getSendAsset,
910
getSendStage,
1011
resetSendState,
@@ -19,15 +20,18 @@ export default function SendHeader() {
1920
const stage = useSelector(getSendStage);
2021
const asset = useSelector(getSendAsset);
2122
const t = useI18nContext();
22-
23+
const draftTransactionExists = useSelector(getDraftTransactionExists);
2324
const onClose = () => {
2425
dispatch(resetSendState());
2526
history.push(mostRecentOverviewPage);
2627
};
2728

28-
let title = asset.type === ASSET_TYPES.NATIVE ? t('send') : t('sendTokens');
29+
let title = asset?.type === ASSET_TYPES.NATIVE ? t('send') : t('sendTokens');
2930

30-
if (stage === SEND_STAGES.ADD_RECIPIENT || stage === SEND_STAGES.INACTIVE) {
31+
if (
32+
draftTransactionExists === false ||
33+
[SEND_STAGES.ADD_RECIPIENT, SEND_STAGES.INACTIVE].includes(stage)
34+
) {
3135
title = t('sendTo');
3236
} else if (stage === SEND_STAGES.EDIT) {
3337
title = t('edit');

ui/pages/send/send.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import React, { useEffect, useCallback, useContext } from 'react';
1+
import React, { useEffect, useCallback, useContext, useRef } from 'react';
22
import { useDispatch, useSelector } from 'react-redux';
33
import { useHistory, useLocation } from 'react-router-dom';
44
import {
55
addHistoryEntry,
6+
getDraftTransactionExists,
67
getIsUsingMyAccountForRecipientSearch,
78
getRecipient,
89
getRecipientUserInput,
910
getSendStage,
1011
resetRecipientInput,
1112
resetSendState,
1213
SEND_STAGES,
14+
startNewDraftTransaction,
1315
updateRecipient,
1416
updateRecipientUserInput,
1517
} from '../../ducks/send';
@@ -18,6 +20,7 @@ import { getSendHexDataFeatureFlagState } from '../../ducks/metamask/metamask';
1820
import { showQrScanner } from '../../store/actions';
1921
import { MetaMetricsContext } from '../../contexts/metametrics';
2022
import { EVENT } from '../../../shared/constants/metametrics';
23+
import { ASSET_TYPES } from '../../../shared/constants/transaction';
2124
import SendHeader from './send-header';
2225
import AddRecipient from './send-content/add-recipient';
2326
import SendContent from './send-content';
@@ -29,6 +32,7 @@ const sendSliceIsCustomPriceExcessive = (state) =>
2932

3033
export default function SendTransactionScreen() {
3134
const history = useHistory();
35+
const startedNewDraftTransaction = useRef(false);
3236
const stage = useSelector(getSendStage);
3337
const gasIsExcessive = useSelector(sendSliceIsCustomPriceExcessive);
3438
const isUsingMyAccountsForRecipientSearch = useSelector(
@@ -37,6 +41,7 @@ export default function SendTransactionScreen() {
3741
const recipient = useSelector(getRecipient);
3842
const showHexData = useSelector(getSendHexDataFeatureFlagState);
3943
const userInput = useSelector(getRecipientUserInput);
44+
const draftTransactionExists = useSelector(getDraftTransactionExists);
4045
const location = useLocation();
4146
const trackEvent = useContext(MetaMetricsContext);
4247

@@ -46,6 +51,23 @@ export default function SendTransactionScreen() {
4651
dispatch(resetSendState());
4752
}, [dispatch]);
4853

54+
/**
55+
* It is possible to route to this page directly, either by typing in the url
56+
* or by clicking the browser back button after progressing to the confirm
57+
* screen. In the case where a draft transaction does not yet exist, this
58+
* hook is responsible for creating it. We will assume that this is a native
59+
* asset send.
60+
*/
61+
useEffect(() => {
62+
if (
63+
draftTransactionExists === false &&
64+
startedNewDraftTransaction.current === false
65+
) {
66+
startedNewDraftTransaction.current = true;
67+
dispatch(startNewDraftTransaction({ type: ASSET_TYPES.NATIVE }));
68+
}
69+
}, [draftTransactionExists, dispatch]);
70+
4971
useEffect(() => {
5072
window.addEventListener('beforeunload', cleanup);
5173
}, [cleanup]);
@@ -70,7 +92,10 @@ export default function SendTransactionScreen() {
7092

7193
let content;
7294

73-
if ([SEND_STAGES.EDIT, SEND_STAGES.DRAFT].includes(stage)) {
95+
if (
96+
draftTransactionExists &&
97+
[SEND_STAGES.EDIT, SEND_STAGES.DRAFT].includes(stage)
98+
) {
7499
content = (
75100
<>
76101
<SendContent

ui/pages/send/send.test.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import configureMockStore from 'redux-mock-store';
33
import thunk from 'redux-thunk';
44

55
import { useLocation } from 'react-router-dom';
6-
import { SEND_STAGES } from '../../ducks/send';
6+
import { SEND_STAGES, startNewDraftTransaction } from '../../ducks/send';
77
import { ensInitialState } from '../../ducks/ens';
88
import { renderWithProvider } from '../../../test/jest';
99
import { RINKEBY_CHAIN_ID } from '../../../shared/constants/network';
@@ -13,6 +13,20 @@ import Send from './send';
1313

1414
const middleware = [thunk];
1515

16+
jest.mock('../../ducks/send/send', () => {
17+
const original = jest.requireActual('../../ducks/send/send');
18+
return {
19+
...original,
20+
// We don't really need to start a draft transaction, and the mock store
21+
// does not update as a result of action calls so instead we just ensure
22+
// that the action WOULD be called.
23+
startNewDraftTransaction: jest.fn(() => ({
24+
type: 'TEST_START_NEW_DRAFT',
25+
payload: null,
26+
})),
27+
};
28+
});
29+
1630
jest.mock('react-router-dom', () => {
1731
const original = jest.requireActual('react-router-dom');
1832
return {
@@ -160,6 +174,25 @@ describe('Send Page', () => {
160174
const { queryByText } = renderWithProvider(<Send />, store);
161175
expect(queryByText('Next')).toBeNull();
162176
});
177+
178+
it('should render correctly even when a draftTransaction does not exist', () => {
179+
const modifiedStore = {
180+
...baseStore,
181+
send: {
182+
...baseStore.send,
183+
currentTransactionUUID: null,
184+
},
185+
};
186+
const store = configureMockStore(middleware)(modifiedStore);
187+
const { getByPlaceholderText } = renderWithProvider(<Send />, store);
188+
// Ensure that the send flow renders on the add recipient screen when
189+
// there is no draft transaction.
190+
expect(
191+
getByPlaceholderText('Search, public address (0x), or ENS'),
192+
).toBeTruthy();
193+
// Ensure we start a new draft transaction when its missing.
194+
expect(startNewDraftTransaction).toHaveBeenCalledTimes(1);
195+
});
163196
});
164197

165198
describe('Send and Edit Flow (draft)', () => {

0 commit comments

Comments
 (0)