Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(trade): persistent form state #16332

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Account>(() => {
if (coinmarketAccount && isNotFormPage) {
if (coinmarketAccount && shouldUseCoinmarketAccount) {
return coinmarketAccount;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -78,6 +79,7 @@ export const useCoinmarketBuyForm = ({
const [isSubmittingHelper, setIsSubmittingHelper] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null);
const { shouldSendInSats } = useBitcoinAmountUnit(account.symbol);
const isPreviousRouteFromTradeSection = useCoinmarketPreviousRoute(type);

const {
defaultValues,
Expand All @@ -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<CoinmarketBuyFormProps>({
mode: 'onChange',
defaultValues: isDraft && draftUpdated ? draftUpdated : defaultValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { useCoinmarketAccount } from 'src/hooks/wallet/coinmarket/form/common/us
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 { useCoinmarketPreviousRoute } from 'src/hooks/wallet/coinmarket/form/common/useCoinmarketPreviousRoute';

import { useCoinmarketInitializer } from './common/useCoinmarketInitializer';

Expand All @@ -85,12 +86,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, type });
Expand Down Expand Up @@ -139,7 +139,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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, type });
Expand Down Expand Up @@ -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 ?? '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
33 changes: 3 additions & 30 deletions packages/suite/src/middlewares/wallet/coinmarketMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { MiddlewareAPI } from 'redux';

import { UI } from '@trezor/connect';
import { Route } from '@suite-common/suite-types';

import { AppState, Action, Dispatch } from 'src/types/suite';
import {
Expand All @@ -17,27 +16,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;
Expand Down Expand Up @@ -80,7 +65,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));
}
Expand Down Expand Up @@ -157,25 +142,13 @@ 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;
const isBuy = routeName === 'wallet-coinmarket-buy';
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'));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
coinmarketGetAccountLabel,
testnetToProdCryptoId,
getAddressAndTokenFromAccountOptionsGroupProps,
getTradeTypeByRoute,
} from 'src/utils/wallet/coinmarket/coinmarketUtils';
import {
FIXTURE_ACCOUNTS,
Expand Down Expand Up @@ -319,4 +320,24 @@ describe('coinmarket utils', () => {
);
});
});

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);
});
});
18 changes: 17 additions & 1 deletion packages/suite/src/utils/wallet/coinmarket/coinmarketUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -540,3 +540,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';
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -230,7 +228,6 @@ export const TokenRow = ({
label: <Translation id="TR_COINMARKET_SELL" />,
icon: 'currencyCircleDollar',
onClick: () => {
dispatch(setCoinmarketSellAccount(account));
dispatch(
setCoinmarketPrefilledFromCryptoId(
tokenCryptoId,
Expand All @@ -250,7 +247,6 @@ export const TokenRow = ({
label: <Translation id="TR_COINMARKET_SWAP" />,
icon: 'arrowsLeftRight',
onClick: () => {
dispatch(setCoinmarketExchangeAccount(account));
dispatch(
setCoinmarketPrefilledFromCryptoId(
tokenCryptoId,
Expand Down Expand Up @@ -444,7 +440,6 @@ export const TokenRow = ({
icon="arrowsLeftRight"
size="small"
onClick={() => {
dispatch(setCoinmarketExchangeAccount(account));
dispatch(setCoinmarketPrefilledFromCryptoId(tokenCryptoId));
goToWithAnalytics('wallet-coinmarket-exchange', {
params: {
Expand Down
Loading