diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index 1f990f0d..1503eb3b 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -24,7 +24,6 @@ import { LifiTransferStarter } from '@/token-bridge-sdk/LifiTransferStarter'; import { getTokenOverride } from '../../app/api/crosschain-transfers/utils'; import { DOCS_DOMAIN, GET_HELP_LINK, PathnameEnum } from '../../constants'; import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatchTransferSupported'; -import { useSetInputAmount } from '../../hooks/TransferPanel/useSetInputAmount'; import { useAccountType } from '../../hooks/useAccountType'; import { TabParamEnum, tabToIndex, useArbQueryParams } from '../../hooks/useArbQueryParams'; import { useBalances } from '../../hooks/useBalances'; @@ -171,8 +170,6 @@ export function TransferPanel() { const isTransferAllowed = useLatest(useIsTransferAllowed()); - const { setAmount, setAmount2 } = useSetInputAmount(); - const latestDestinationAddress = useLatest(destinationAddress); const [dialogProps, openDialog] = useDialog2(); @@ -192,13 +189,13 @@ export function TransferPanel() { const { handleError } = useError(); - const switchToTransactionHistoryTab = useCallback( - () => - setQueryParams({ - tab: tabToIndex[TabParamEnum.TX_HISTORY], - }), - [setQueryParams], - ); + const resetAmountAndSwitchToTransactionHistoryTab = useCallback(() => { + setQueryParams({ + tab: tabToIndex[TabParamEnum.TX_HISTORY], + amount: '', + amount2: '', + }); + }, [setQueryParams]); useEffect(() => { if (importTokenModalStatus !== ImportTokenModalStatus.IDLE) { @@ -214,12 +211,6 @@ export function TransferPanel() { tokenImportDialogProps.onClose(false); } - function clearAmountInput() { - // clear amount input on transfer panel - setAmount(''); - setAmount2(''); - } - const isTokenAlreadyImported = useMemo(() => { if (typeof tokenFromSearchParams === 'undefined') { return true; @@ -512,9 +503,8 @@ export function TransferPanel() { }; addPendingTransaction(newTransfer); - switchToTransactionHistoryTab(); setTransferring(false); - clearAmountInput(); + resetAmountAndSwitchToTransactionHistoryTab(); clearRoute(); } catch (e) { } finally { @@ -637,9 +627,10 @@ export function TransferPanel() { tag: selectedRoute, }); + resetAmountAndSwitchToTransactionHistoryTab(); + if (isSmartContractWallet) { // show the warning in case of SCW since we cannot show Lifi tx history for SCW - switchToTransactionHistoryTab(); setTimeout(() => { highlightTransactionHistoryDisclaimer(); }, 100); @@ -681,10 +672,8 @@ export function TransferPanel() { }; addPendingTransaction(newTransfer); addLifiTransactionToCache(newTransfer); - switchToTransactionHistoryTab(); } - clearAmountInput(); clearRoute(); } catch (error) { if (isUserRejectedError(error)) { @@ -784,8 +773,7 @@ export function TransferPanel() { destinationChain: getNetworkName(networks.destinationChain.id), }); - switchToTransactionHistoryTab(); - clearAmountInput(); + resetAmountAndSwitchToTransactionHistoryTab(); if (isSmartContractWallet) { // show the warning in case of SCW since we don't cannot show OFT tx history @@ -1158,12 +1146,17 @@ export function TransferPanel() { if (embedMode) { openDialog('widget_transaction_history'); } else { - switchToTransactionHistoryTab(); + setQueryParams({ + tab: tabToIndex[TabParamEnum.TX_HISTORY], + }); } setTransferring(false); clearRoute(); - clearAmountInput(); + setQueryParams({ + amount: '', + amount2: '', + }); await (sourceChainTransaction as TransactionResponse).wait(); diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index dd57d2e3..b8349500 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -19,7 +19,6 @@ import { useCallback } from 'react'; import { BooleanParam, DecodedValueMap, - QueryParamConfigMap, QueryParamOptions, QueryParamProvider, SetQuery, @@ -69,27 +68,47 @@ export { TabParam, }; +export const queryParamProviderOptions = { + searchStringToObject: queryString.parse, + objectToSearchString: queryString.stringify, + updateType: 'replaceIn', // replace just a single parameter when updating query-state, leaving the rest as is + removeDefaultsFromUrl: true, + enableBatching: true, + params: { + sourceChain: ChainParam, + destinationChain: ChainParam, + amount: withDefault(AmountQueryParam, ''), // amount which is filled in Transfer panel + amount2: withDefault(AmountQueryParam, ''), // extra eth to send together with erc20 + destinationAddress: withDefault(StringParam, undefined), + token: TokenQueryParam, // import a new token using a Dialog Box + settingsOpen: withDefault(BooleanParam, false), + tab: withDefault(TabParam, tabToIndex[TabParamEnum.BRIDGE]), // which tab is active + disabledFeatures: withDefault(DisabledFeaturesParam, []), // disabled features in the bridge + theme: withDefault(ThemeParam, defaultTheme), // theme customization + }, +} as const satisfies QueryParamOptions; + +type ArbQueryParamConfigMap = typeof queryParamProviderOptions.params; +type PartialArbQueryParams = Partial>; + /** * We use variables outside of the hook to share the accumulator accross multiple calls of useArbQueryParams */ -let pendingUpdates: DecodedValueMap = { +let pendingUpdates: PartialArbQueryParams & Record = { /** If no sanitization happened on the server, set a flag on first change of query param to avoid infinite loop */ sanitized: 'true', }; let debounceTimeout: NodeJS.Timeout | null = null; export type SetQueryParamsParameters = - | Partial> - | (( - latestValues: DecodedValueMap, - ) => Partial>); + | PartialArbQueryParams + | ((latestValues: DecodedValueMap) => PartialArbQueryParams); const debouncedUpdateQueryParams = ( updates: SetQueryParamsParameters, - originalSetQueryParams: SetQuery, + originalSetQueryParams: SetQuery, /** debounce only applies to object update, for function updates it will be called immediately */ debounce: boolean = false, ) => { - // Handle function update: setQueryParams((prevState) => ({ ...prevState, ...newUpdate })) if (typeof updates === 'function') { if (debounceTimeout) { clearTimeout(debounceTimeout); @@ -98,25 +117,25 @@ const debouncedUpdateQueryParams = ( originalSetQueryParams((prevState) => updates({ ...prevState, ...pendingUpdates })); pendingUpdates = {}; - } else { - // Handle classic object updates: setQueryParams({ amount: "0.1" }) - pendingUpdates = { ...pendingUpdates, ...updates }; + return; + } - if (debounceTimeout) { - clearTimeout(debounceTimeout); - } + pendingUpdates = { ...pendingUpdates, ...updates }; - if (debounce) { - debounceTimeout = setTimeout(() => { - originalSetQueryParams(pendingUpdates); - pendingUpdates = {}; - debounceTimeout = null; - }, 400); - } else { + if (debounceTimeout) { + clearTimeout(debounceTimeout); + } + + if (debounce) { + debounceTimeout = setTimeout(() => { originalSetQueryParams(pendingUpdates); pendingUpdates = {}; debounceTimeout = null; - } + }, 400); + } else { + originalSetQueryParams(pendingUpdates); + pendingUpdates = {}; + debounceTimeout = null; } }; @@ -127,7 +146,7 @@ export const useArbQueryParams = () => { setQueryParams (setter for all query state variables with debounced accumulator) ] */ - const [queryParams, setQueryParams] = useQueryParams(); + const [queryParams, setQueryParams] = useQueryParams(); const debouncedSetQueryParams = useCallback( (updates: SetQueryParamsParameters, { debounce }: { debounce?: boolean } = {}) => @@ -138,26 +157,6 @@ export const useArbQueryParams = () => { return [queryParams, debouncedSetQueryParams] as const; }; -export const queryParamProviderOptions = { - searchStringToObject: queryString.parse, - objectToSearchString: queryString.stringify, - updateType: 'replaceIn', // replace just a single parameter when updating query-state, leaving the rest as is - removeDefaultsFromUrl: true, - enableBatching: true, - params: { - sourceChain: ChainParam, - destinationChain: ChainParam, - amount: withDefault(AmountQueryParam, ''), // amount which is filled in Transfer panel - amount2: withDefault(AmountQueryParam, ''), // extra eth to send together with erc20 - destinationAddress: withDefault(StringParam, undefined), - token: TokenQueryParam, // import a new token using a Dialog Box - settingsOpen: withDefault(BooleanParam, false), - tab: withDefault(TabParam, tabToIndex[TabParamEnum.BRIDGE]), // which tab is active - disabledFeatures: withDefault(DisabledFeaturesParam, []), // disabled features in the bridge - theme: withDefault(ThemeParam, defaultTheme), // theme customization - }, -} as const satisfies QueryParamOptions; - export function ArbQueryParamProvider({ children }: { children: React.ReactNode }) { return (