From 4839c7b0ab8b1aca6169e3623fb90a5d14a599de Mon Sep 17 00:00:00 2001 From: Martin Homola Date: Wed, 22 Jan 2025 13:38:51 +0100 Subject: [PATCH] fix(trade): persistent form state --- .../form/common/useCoinmarketAccount.ts | 15 +++++---- .../form/common/useCoinmarketPreviousRoute.ts | 14 ++++++++ .../coinmarket/form/useCoinmarketBuyForm.tsx | 7 +++- .../form/useCoinmarketExchangeForm.ts | 8 ++--- .../coinmarket/form/useCoinmarketSellForm.ts | 7 ++-- .../__tests__/coinmarketMiddleware.test.ts | 27 --------------- .../wallet/coinmarketMiddleware.ts | 33 ++----------------- .../__tests__/coinmarketUtils.test.ts | 21 ++++++++++++ .../wallet/coinmarket/coinmarketUtils.ts | 18 +++++++++- .../CoinmarketFormInputCryptoSelect.tsx | 6 ++-- .../tokens/common/TokensTable/TokenRow.tsx | 5 --- 11 files changed, 81 insertions(+), 80 deletions(-) create mode 100644 packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute.ts diff --git a/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketAccount.ts b/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketAccount.ts index eb763b39097..93d37e4ccd0 100644 --- a/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketAccount.ts +++ b/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketAccount.ts @@ -13,22 +13,23 @@ import { interface CoinmarketUseAccountProps { coinmarketAccount: Account | undefined; selectedAccount: SelectedAccountLoaded; - isNotFormPage?: boolean; + shouldUseCoinmarketAccount?: boolean; } - +/** + * Hook used to get account for trade form (used in Sell/Swap) + * - coinmarketAccount is used whether user is in the trading flow (persistent) + * - selectedAccount is used as initial state if user entries from different page than trade + */ export const useCoinmarketAccount = ({ coinmarketAccount, selectedAccount, - isNotFormPage, + shouldUseCoinmarketAccount, }: CoinmarketUseAccountProps): [Account, (state: Account) => void] => { const accounts = useSelector(selectAccounts); const device = useSelector(selectSelectedDevice); - // coinmarketAccount is used on offers page - // if is testnet, use - // selectedAccount is used as initial state if this is form page const [account, setAccount] = useState(() => { - if (coinmarketAccount && isNotFormPage) { + if (coinmarketAccount && shouldUseCoinmarketAccount) { return coinmarketAccount; } diff --git a/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute.ts b/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute.ts new file mode 100644 index 00000000000..750529260d3 --- /dev/null +++ b/packages/suite/src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute.ts @@ -0,0 +1,14 @@ +import { useSelector } from 'src/hooks/suite'; +import { selectRouter } from 'src/reducers/suite/routerReducer'; +import { CoinmarketTradeType } from 'src/types/coinmarket/coinmarket'; +import { getTradeTypeByRoute } from 'src/utils/wallet/coinmarket/coinmarketUtils'; + +export const useCoinmarketPreviousRoute = (tradeType: CoinmarketTradeType) => { + const { + settingsBackRoute: { name: previousRouteName }, + } = useSelector(selectRouter); + const tradeTypeFromRoute = getTradeTypeByRoute(previousRouteName); + const isPreviousRouteFromTradeSection = tradeTypeFromRoute === tradeType; + + return isPreviousRouteFromTradeSection; +}; diff --git a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketBuyForm.tsx b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketBuyForm.tsx index b453bfc218d..26d3f05a627 100644 --- a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketBuyForm.tsx +++ b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketBuyForm.tsx @@ -50,6 +50,7 @@ import { useCoinmarketModalCrypto } from 'src/hooks/wallet/coinmarket/form/commo import { useCoinmarketInfo } from 'src/hooks/wallet/coinmarket/useCoinmarketInfo'; import { useCoinmarketBuyFormDefaultValues } from 'src/hooks/wallet/coinmarket/form/useCoinmarketBuyFormDefaultValues'; import type { AmountLimitProps } from 'src/utils/suite/validation'; +import { useCoinmarketPreviousRoute } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute'; import { useCoinmarketInitializer } from './common/useCoinmarketInitializer'; @@ -78,6 +79,7 @@ export const useCoinmarketBuyForm = ({ const [isSubmittingHelper, setIsSubmittingHelper] = useState(false); const abortControllerRef = useRef(null); const { shouldSendInSats } = useBitcoinAmountUnit(account.symbol); + const isPreviousRouteFromTradeSection = useCoinmarketPreviousRoute(type); const { defaultValues, @@ -98,11 +100,14 @@ export const useCoinmarketBuyForm = ({ ? draft.fiatInput : buyInfo?.buyInfo?.defaultAmountsOfFiatCurrencies.get(suggestedFiatCurrency), // remember only for offers page - cryptoSelect: pageType === 'form' ? defaultValues.cryptoSelect : draft.cryptoSelect, + cryptoSelect: isPreviousRouteFromTradeSection + ? draft.cryptoSelect + : defaultValues.cryptoSelect, } : null; const isDraft = !!draftUpdated || !!isNotFormPage; + const methods = useForm({ mode: 'onChange', defaultValues: isDraft && draftUpdated ? draftUpdated : defaultValues, diff --git a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketExchangeForm.ts b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketExchangeForm.ts index a43bd5cdf7a..569e41bcff2 100644 --- a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketExchangeForm.ts +++ b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketExchangeForm.ts @@ -59,6 +59,7 @@ import { useCoinmarketInfo } from 'src/hooks/wallet/coinmarket/useCoinmarketInfo import { useCoinmarketFiatValues } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketFiatValues'; import type { CryptoAmountLimitProps } from 'src/utils/suite/validation'; import { useCoinmarketExchangeQuotesFilter } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketExchangeQuotesFilter'; +import { useCoinmarketPreviousRoute } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute'; import { useCoinmarketInitializer } from './common/useCoinmarketInitializer'; @@ -79,12 +80,11 @@ export const useCoinmarketExchangeForm = ({ addressVerified, } = useSelector(state => state.wallet.coinmarket.exchange); const { cryptoIdToCoinSymbol } = useCoinmarketInfo(); - // selectedAccount is used as initial state if this is form page - // coinmarketAccount is used on offers page + const isPreviousRouteFromTradeSection = useCoinmarketPreviousRoute(type); const [account, setAccount] = useCoinmarketAccount({ coinmarketAccount, selectedAccount, - isNotFormPage, + shouldUseCoinmarketAccount: isPreviousRouteFromTradeSection, }); const { callInProgress, timer, device, setCallInProgress, checkQuotesTimer } = useCoinmarketInitializer({ selectedAccount, pageType }); @@ -133,7 +133,7 @@ export const useCoinmarketExchangeForm = ({ const isDraft = !!draft; const getDraftUpdated = (): CoinmarketExchangeFormProps | null => { if (!draft) return null; - if (isNotFormPage) return draft; + if (isPreviousRouteFromTradeSection) return draft; const defaultReceiveCryptoSelect = coinmarketGetExchangeReceiveCryptoId( defaultValues.sendCryptoSelect?.value, diff --git a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketSellForm.ts b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketSellForm.ts index 3fdc4a9d54b..9bf4204ee90 100644 --- a/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketSellForm.ts +++ b/packages/suite/src/hooks/wallet/coinmarket/form/useCoinmarketSellForm.ts @@ -55,6 +55,7 @@ import { useCoinmarketCurrencySwitcher } from 'src/hooks/wallet/coinmarket/form/ import { useCoinmarketAccount } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketAccount'; import { useCoinmarketInfo } from 'src/hooks/wallet/coinmarket/useCoinmarketInfo'; import type { AmountLimitProps } from 'src/utils/suite/validation'; +import { useCoinmarketPreviousRoute } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute'; import { useCoinmarketInitializer } from './common/useCoinmarketInitializer'; @@ -75,11 +76,11 @@ export const useCoinmarketSellForm = ({ selectedQuote, } = useSelector(state => state.wallet.coinmarket.sell); const { cryptoIdToCoinSymbol } = useCoinmarketInfo(); - + const isPreviousRouteFromTradeSection = useCoinmarketPreviousRoute(type); const [account, setAccount] = useCoinmarketAccount({ coinmarketAccount, selectedAccount, - isNotFormPage, + shouldUseCoinmarketAccount: isPreviousRouteFromTradeSection, }); const { callInProgress, timer, device, setCallInProgress, checkQuotesTimer } = useCoinmarketInitializer({ selectedAccount, pageType }); @@ -119,7 +120,7 @@ export const useCoinmarketSellForm = ({ const draft = getDraft(sellDraftKey); const getDraftUpdated = (): CoinmarketSellFormProps | null => { if (!draft) return null; - if (isNotFormPage) { + if (isPreviousRouteFromTradeSection) { const outputs = draft.outputs?.map(output => ({ ...output, fiat: output.fiat ?? '', diff --git a/packages/suite/src/middlewares/wallet/__tests__/coinmarketMiddleware.test.ts b/packages/suite/src/middlewares/wallet/__tests__/coinmarketMiddleware.test.ts index e4d82fa9f47..c9d19c71bc0 100644 --- a/packages/suite/src/middlewares/wallet/__tests__/coinmarketMiddleware.test.ts +++ b/packages/suite/src/middlewares/wallet/__tests__/coinmarketMiddleware.test.ts @@ -297,33 +297,6 @@ describe('coinmarketMiddleware', () => { expect(store.getState().wallet.coinmarket.modalAccount).toEqual(undefined); }); - it('Test of cleaning coinmarketAccount property', () => { - const store = initStore( - getInitialState({ - coinmarket: { - ...initialState, - exchange: { - ...initialState.exchange, - coinmarketAccount: accounts[0], - }, - sell: { - ...initialState.sell, - coinmarketAccount: accounts[0], - }, - }, - }), - ); - - // go to coinmarket - store.dispatch({ - type: ROUTER.LOCATION_CHANGE, - payload: COINMARKET_EXCHANGE_ROUTE, - }); - - expect(store.getState().wallet.coinmarket.sell.coinmarketAccount).toBe(accounts[0]); - expect(store.getState().wallet.coinmarket.exchange.coinmarketAccount).toEqual(undefined); - }); - it('Test of setting activeSection after changing route', () => { const store = initStore( getInitialState({ diff --git a/packages/suite/src/middlewares/wallet/coinmarketMiddleware.ts b/packages/suite/src/middlewares/wallet/coinmarketMiddleware.ts index e9669d16e7c..72c3decf2f7 100644 --- a/packages/suite/src/middlewares/wallet/coinmarketMiddleware.ts +++ b/packages/suite/src/middlewares/wallet/coinmarketMiddleware.ts @@ -1,7 +1,6 @@ import { MiddlewareAPI } from 'redux'; import { UI } from '@trezor/connect'; -import { Route } from '@suite-common/suite-types'; import { accountsActions } from '@suite-common/wallet-core'; import { AppState, Action, Dispatch } from 'src/types/suite'; @@ -18,27 +17,13 @@ import * as coinmarketBuyActions from 'src/actions/wallet/coinmarketBuyActions'; import * as coinmarketExchangeActions from 'src/actions/wallet/coinmarketExchangeActions'; import * as coinmarketSellActions from 'src/actions/wallet/coinmarketSellActions'; import { ROUTER, MODAL } from 'src/actions/suite/constants'; -import { CoinmarketTradeType } from 'src/types/coinmarket/coinmarket'; - -const getTradeTypeByRoute = (route: Route | undefined): CoinmarketTradeType | undefined => { - if (route?.name.startsWith('wallet-coinmarket-buy')) { - return 'buy'; - } - - if (route?.name.startsWith('wallet-coinmarket-sell')) { - return 'sell'; - } - - if (route?.name.startsWith('wallet-coinmarket-exchange')) { - return 'exchange'; - } -}; +import { getTradeTypeByRoute } from 'src/utils/wallet/coinmarket/coinmarketUtils'; /** * In the Sell and Swap section an account can be changed by a user in the select */ export const getAccountAccordingToRoute = (state: AppState) => { - const tradeType = getTradeTypeByRoute(state.router.route); + const tradeType = getTradeTypeByRoute(state.router.route?.name); const { account } = state.wallet.selectedAccount; const sellSelectedAccount = state.wallet.coinmarket.sell.coinmarketAccount; @@ -81,7 +66,7 @@ export const coinmarketMiddleware = invityAPI.setInvityServersEnvironment(invityServerEnvironment); } - const tradeType = getTradeTypeByRoute(state.router.route); + const tradeType = getTradeTypeByRoute(state.router.route?.name); if (tradeType) { api.dispatch(coinmarketCommonActions.setActiveSection(tradeType)); } @@ -158,8 +143,6 @@ export const coinmarketMiddleware = // get the new state after the action has been processed const newState = api.getState(); - const sellCoinmarketAccount = newState.wallet.coinmarket.sell.coinmarketAccount; - const exchangeCoinmarketAccount = newState.wallet.coinmarket.exchange.coinmarketAccount; if (action.type === ROUTER.LOCATION_CHANGE) { const routeName = newState.router.route?.name; @@ -167,16 +150,6 @@ export const coinmarketMiddleware = const isSell = routeName === 'wallet-coinmarket-sell'; const isExchange = routeName === 'wallet-coinmarket-exchange'; - // clean coinmarketAccount in sell - if (isSell && sellCoinmarketAccount) { - api.dispatch(coinmarketSellActions.setCoinmarketSellAccount(undefined)); - } - - // clean coinmarketAccount in exchange - if (isExchange && exchangeCoinmarketAccount) { - api.dispatch(coinmarketExchangeActions.setCoinmarketExchangeAccount(undefined)); - } - if (isBuy) { api.dispatch(coinmarketCommonActions.setActiveSection('buy')); } diff --git a/packages/suite/src/utils/wallet/coinmarket/__tests__/coinmarketUtils.test.ts b/packages/suite/src/utils/wallet/coinmarket/__tests__/coinmarketUtils.test.ts index 65530216368..34fe7fd417b 100644 --- a/packages/suite/src/utils/wallet/coinmarket/__tests__/coinmarketUtils.test.ts +++ b/packages/suite/src/utils/wallet/coinmarket/__tests__/coinmarketUtils.test.ts @@ -18,6 +18,7 @@ import { testnetToProdCryptoId, getAddressAndTokenFromAccountOptionsGroupProps, isCryptoIdForNativeToken, + getTradeTypeByRoute, } from 'src/utils/wallet/coinmarket/coinmarketUtils'; import { FIXTURE_ACCOUNTS, @@ -339,4 +340,24 @@ describe('coinmarket utils', () => { ), ).toEqual(true); }); + + it('getTradeTypeByRoute - testing correct returning trade section according to route', () => { + expect(getTradeTypeByRoute('wallet-coinmarket-buy')).toEqual('buy'); + expect(getTradeTypeByRoute('wallet-coinmarket-buy-detail')).toEqual('buy'); + expect(getTradeTypeByRoute('wallet-coinmarket-buy-offers')).toEqual('buy'); + expect(getTradeTypeByRoute('wallet-coinmarket-buy-confirm')).toEqual('buy'); + + expect(getTradeTypeByRoute('wallet-coinmarket-sell')).toEqual('sell'); + expect(getTradeTypeByRoute('wallet-coinmarket-sell-detail')).toEqual('sell'); + expect(getTradeTypeByRoute('wallet-coinmarket-sell-offers')).toEqual('sell'); + expect(getTradeTypeByRoute('wallet-coinmarket-sell-confirm')).toEqual('sell'); + + expect(getTradeTypeByRoute('wallet-coinmarket-exchange')).toEqual('exchange'); + expect(getTradeTypeByRoute('wallet-coinmarket-exchange-detail')).toEqual('exchange'); + expect(getTradeTypeByRoute('wallet-coinmarket-exchange-offers')).toEqual('exchange'); + expect(getTradeTypeByRoute('wallet-coinmarket-exchange-confirm')).toEqual('exchange'); + + expect(getTradeTypeByRoute('wallet-coinmarket-dca')).toEqual(undefined); + expect(getTradeTypeByRoute('wallet-index')).toEqual(undefined); + }); }); diff --git a/packages/suite/src/utils/wallet/coinmarket/coinmarketUtils.ts b/packages/suite/src/utils/wallet/coinmarket/coinmarketUtils.ts index a845dafba5c..612618913a6 100644 --- a/packages/suite/src/utils/wallet/coinmarket/coinmarketUtils.ts +++ b/packages/suite/src/utils/wallet/coinmarket/coinmarketUtils.ts @@ -24,7 +24,7 @@ import { BigNumber } from '@trezor/utils'; import { Account } from 'src/types/wallet'; import regional from 'src/constants/wallet/coinmarket/regional'; -import { ExtendedMessageDescriptor, TrezorDevice } from 'src/types/suite'; +import { ExtendedMessageDescriptor, Route, TrezorDevice } from 'src/types/suite'; import { CoinmarketAccountOptionsGroupOptionProps, CoinmarketAccountsOptionsGroupProps, @@ -541,3 +541,19 @@ export const getAddressAndTokenFromAccountOptionsGroupProps = ( return { address: '', token: selected.contractAddress ?? null }; }; + +export const getTradeTypeByRoute = ( + routeName: Route['name'] | undefined, +): CoinmarketTradeType | undefined => { + if (routeName?.startsWith('wallet-coinmarket-buy')) { + return 'buy'; + } + + if (routeName?.startsWith('wallet-coinmarket-sell')) { + return 'sell'; + } + + if (routeName?.startsWith('wallet-coinmarket-exchange')) { + return 'exchange'; + } +}; diff --git a/packages/suite/src/views/wallet/coinmarket/common/CoinmarketForm/CoinmarketFormInput/CoinmarketFormInputCryptoSelect.tsx b/packages/suite/src/views/wallet/coinmarket/common/CoinmarketForm/CoinmarketFormInput/CoinmarketFormInputCryptoSelect.tsx index 314d451519e..8458469f806 100644 --- a/packages/suite/src/views/wallet/coinmarket/common/CoinmarketForm/CoinmarketFormInput/CoinmarketFormInputCryptoSelect.tsx +++ b/packages/suite/src/views/wallet/coinmarket/common/CoinmarketForm/CoinmarketFormInput/CoinmarketFormInputCryptoSelect.tsx @@ -154,9 +154,11 @@ export const CoinmarketFormInputCryptoSelect = < if (!findOption) return; if (isCoinmarketExchangeContext(context)) { - context.setValue(FORM_RECEIVE_CRYPTO_CURRENCY_SELECT, findOption); + context.setValue(FORM_RECEIVE_CRYPTO_CURRENCY_SELECT, findOption, { + shouldDirty: true, + }); } else { - context.setValue(FORM_CRYPTO_CURRENCY_SELECT, findOption); + context.setValue(FORM_CRYPTO_CURRENCY_SELECT, findOption, { shouldDirty: true }); } context.setAmountLimits(undefined); diff --git a/packages/suite/src/views/wallet/tokens/common/TokensTable/TokenRow.tsx b/packages/suite/src/views/wallet/tokens/common/TokensTable/TokenRow.tsx index 8ad740228eb..25dd3274029 100644 --- a/packages/suite/src/views/wallet/tokens/common/TokensTable/TokenRow.tsx +++ b/packages/suite/src/views/wallet/tokens/common/TokensTable/TokenRow.tsx @@ -58,9 +58,7 @@ import { } from 'src/reducers/suite/suiteReducer'; import { SUITE } from 'src/actions/suite/constants'; import { copyAddressToClipboard, showCopyAddressModal } from 'src/actions/suite/copyAddressActions'; -import { setCoinmarketExchangeAccount } from 'src/actions/wallet/coinmarketExchangeActions'; import { setCoinmarketPrefilledFromCryptoId } from 'src/actions/wallet/coinmarket/coinmarketCommonActions'; -import { setCoinmarketSellAccount } from 'src/actions/wallet/coinmarketSellActions'; import { BlurUrls } from '../BlurUrls'; @@ -230,7 +228,6 @@ export const TokenRow = ({ label: , icon: 'currencyCircleDollar', onClick: () => { - dispatch(setCoinmarketSellAccount(account)); dispatch( setCoinmarketPrefilledFromCryptoId( tokenCryptoId, @@ -250,7 +247,6 @@ export const TokenRow = ({ label: , icon: 'arrowsLeftRight', onClick: () => { - dispatch(setCoinmarketExchangeAccount(account)); dispatch( setCoinmarketPrefilledFromCryptoId( tokenCryptoId, @@ -444,7 +440,6 @@ export const TokenRow = ({ icon="arrowsLeftRight" size="small" onClick={() => { - dispatch(setCoinmarketExchangeAccount(account)); dispatch(setCoinmarketPrefilledFromCryptoId(tokenCryptoId)); goToWithAnalytics('wallet-coinmarket-exchange', { params: {