diff --git a/package.json b/package.json index a6d970ad8..719f002f1 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-dom": "v18", "react-feather": "^2.0.9", "react-hotkeys-hook": "^5.1.0", + "react-number-format": "^5.4.4", "react-pro-sidebar": "^1.1.0", "react-router-dom": "v5", "react-tooltip": "^5.18.0", diff --git a/src/hooks/index.ts b/src/hooks/index.ts index daec305b5..a406ea5ac 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -19,4 +19,3 @@ export * from "./useAmplitude"; export * from "./useRewardSummary"; export * from "./useElapsedSeconds"; export * from "./feature-flags/useFeatureFlag"; -export * from "./useTokenInput"; diff --git a/src/hooks/useTokenInput.ts b/src/hooks/useTokenInput.ts index 9db9907e1..e69de29bb 100644 --- a/src/hooks/useTokenInput.ts +++ b/src/hooks/useTokenInput.ts @@ -1,207 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { BigNumber, utils } from "ethers"; -import { convertTokenToUSD, convertUSDToToken } from "utils"; -import { EnrichedToken } from "views/SwapAndBridge/components/ChainTokenSelector/ChainTokenSelectorModal"; -import { formatUnitsWithMaxFractions } from "utils"; - -export type UnitType = "usd" | "token"; - -type UseTokenInputProps = { - token: EnrichedToken | null; - setAmount: (amount: BigNumber | null) => void; - expectedAmount: BigNumber | undefined; - shouldUpdate: boolean; - isUpdateLoading: boolean; - // Optional: Allow unit state to be controlled from parent - unit?: UnitType; - setUnit?: (unit: UnitType) => void; -}; - -type UseTokenInputReturn = { - amountString: string; - setAmountString: (value: string) => void; - unit: UnitType; - convertedAmount: BigNumber | undefined; - toggleUnit: () => void; - handleInputChange: (value: string) => void; - handleBalanceClick: (amount: BigNumber, decimals: number) => void; -}; - -export function useTokenInput({ - token, - setAmount, - expectedAmount, - shouldUpdate, - isUpdateLoading, - unit: externalUnit, - setUnit: externalSetUnit, -}: UseTokenInputProps): UseTokenInputReturn { - const [amountString, setAmountString] = useState(""); - const [internalUnit, setInternalUnit] = useState("token"); - const [convertedAmount, setConvertedAmount] = useState(); - const [justTyped, setJustTyped] = useState(false); - - // Use external unit if provided, otherwise use internal state - const unit = externalUnit ?? internalUnit; - const setUnit = externalSetUnit ?? setInternalUnit; - - // Handle user input changes - propagate to parent - useEffect(() => { - if (!justTyped) { - return; - } - setJustTyped(false); - try { - if (!token) { - setAmount(null); - return; - } - // If the input is empty or effectively zero, set amount to null - if (!amountString || !Number(amountString)) { - setAmount(null); - return; - } - if (unit === "token") { - const parsed = utils.parseUnits(amountString, token.decimals); - // If parsed amount is zero or negative, set to null - if (parsed.lte(0)) { - setAmount(null); - return; - } - setAmount(parsed); - } else { - const tokenValue = convertUSDToToken(amountString, token); - // If converted value is zero or negative, set to null - if (tokenValue.lte(0)) { - setAmount(null); - return; - } - setAmount(tokenValue); - } - } catch (e) { - setAmount(null); - } - }, [amountString, justTyped, token, unit, setAmount]); - - // Handle quote updates - only update the field that should receive the quote - useEffect(() => { - if (shouldUpdate && isUpdateLoading) { - setAmountString(""); - } - - if (shouldUpdate && token) { - // Clear the field when there's no expected amount and not loading - if (!expectedAmount && !isUpdateLoading) { - setAmountString(""); - } else { - if (expectedAmount) { - if (unit === "token") { - // Display as token amount - setAmountString( - formatUnitsWithMaxFractions(expectedAmount, token.decimals) - ); - } else { - // Display as USD amount - convert token to USD - const tokenAmountFormatted = formatUnitsWithMaxFractions( - expectedAmount, - token.decimals - ); - const usdValue = convertTokenToUSD(tokenAmountFormatted, token); - // convertTokenToUSD returns in 18 decimal precision - setAmountString(utils.formatUnits(usdValue, 18)); - } - } - } - } - }, [expectedAmount, isUpdateLoading, shouldUpdate, token, unit]); - - // Set converted value for display - useEffect(() => { - if (!token || !amountString) { - setConvertedAmount(undefined); - return; - } - try { - if (unit === "token") { - // User typed token amount - convert to USD for display - const usdValue = convertTokenToUSD(amountString, token); - setConvertedAmount(usdValue); - } else { - // User typed USD amount - convert to token for display - const tokenValue = convertUSDToToken(amountString, token); - setConvertedAmount(tokenValue); - } - } catch (e) { - // getting an underflow error here - setConvertedAmount(undefined); - } - }, [token, amountString, unit]); - - // Toggle between token and USD units - const toggleUnit = useCallback(() => { - if (unit === "token") { - // Convert token amount to USD string for display - if (amountString && token && convertedAmount) { - try { - // convertedAmount is USD value in 18 decimals - const a = utils.formatUnits(convertedAmount, 18); - setAmountString(a); - } catch (e) { - setAmountString("0"); - } - } - setUnit("usd"); - } else { - // Convert USD amount to token string for display - if (amountString && token && convertedAmount) { - try { - // convertedAmount is token value in token's native decimals - const a = utils.formatUnits(convertedAmount, token.decimals); - setAmountString(a); - } catch (e) { - setAmountString("0"); - } - } - setUnit("token"); - } - }, [unit, amountString, token, convertedAmount, setUnit]); - - // Handle input field changes - const handleInputChange = useCallback((value: string) => { - if (value === "" || /^\d*\.?\d*$/.test(value)) { - setJustTyped(true); - setAmountString(value); - } - }, []); - - // Handle balance selector click - const handleBalanceClick = useCallback( - (amount: BigNumber, decimals: number) => { - setAmount(amount); - if (unit === "usd" && token) { - // Convert token amount to USD for display - const tokenAmountFormatted = formatUnitsWithMaxFractions( - amount, - decimals - ); - const usdValue = convertTokenToUSD(tokenAmountFormatted, token); - // convertTokenToUSD returns in 18 decimal precision - setAmountString(utils.formatUnits(usdValue, 18)); - } else { - // Display as token amount - setAmountString(formatUnitsWithMaxFractions(amount, decimals)); - } - }, - [setAmount, unit, token] - ); - - return { - amountString, - setAmountString, - unit, - convertedAmount, - toggleUnit, - handleInputChange, - handleBalanceClick, - }; -} diff --git a/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx b/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx index f76f556ce..b06a59b07 100644 --- a/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx +++ b/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx @@ -207,7 +207,7 @@ const createQuoteWithProvider = ( const meta: Meta = { component: ConfirmationButton, - title: "Stories/ConfirmationButton", + title: "Bridge/ConfirmationButton", argTypes: { isQuoteLoading: { control: { type: "boolean" }, diff --git a/src/views/SwapAndBridge/components/InputForm.stories.tsx b/src/views/SwapAndBridge/components/InputForm.stories.tsx new file mode 100644 index 000000000..17d55c889 --- /dev/null +++ b/src/views/SwapAndBridge/components/InputForm.stories.tsx @@ -0,0 +1,182 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { userEvent, within } from "@storybook/testing-library"; +import { BigNumber } from "ethers"; +import { InputForm } from "./InputForm"; +import { EnrichedToken } from "./ChainTokenSelector/ChainTokenSelectorModal"; +import { QuoteRequestProvider } from "../hooks/useQuoteRequest/QuoteRequestContext"; +import { QuoteRequest } from "../hooks/useQuoteRequest/quoteRequestAction"; + +const mockInputToken: EnrichedToken = { + chainId: 1, + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + priceUSD: "1.00", + coinKey: "usdc", + logoURI: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + routeSource: "bridge", + balance: BigNumber.from("1000000000"), + balanceUsd: 1000, +}; + +const mockOutputToken: EnrichedToken = { + chainId: 42161, + address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + priceUSD: "1.00", + coinKey: "usdc", + logoURI: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + routeSource: "bridge", + balance: BigNumber.from("500000000"), + balanceUsd: 500, +}; + +const mockQuoteRequest: QuoteRequest = { + tradeType: "exactInput", + originToken: mockInputToken, + destinationToken: mockOutputToken, + customDestinationAccount: null, + amount: BigNumber.from("100000000"), +}; + +const mockQuoteRequestNoTokens: QuoteRequest = { + tradeType: "exactInput", + originToken: null, + destinationToken: null, + customDestinationAccount: null, + amount: null, +}; + +const mockQuoteRequestMinOutput: QuoteRequest = { + tradeType: "minOutput", + originToken: mockInputToken, + destinationToken: mockOutputToken, + customDestinationAccount: null, + amount: BigNumber.from("99500000"), +}; + +const meta: Meta = { + component: InputForm, + title: "Bridge/InputForm", + argTypes: { + isQuoteLoading: { + control: { type: "boolean" }, + }, + }, + decorators: [ + (Story, context) => ( + +
+ +
+
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: BigNumber.from("99500000"), + }, +}; + +export const Loading: Story = { + args: { + isQuoteLoading: true, + expectedInputAmount: undefined, + expectedOutputAmount: undefined, + }, +}; + +export const WithQuoteResult: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: BigNumber.from("99500000"), + }, +}; + +export const MinOutputMode: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: BigNumber.from("100500000"), + expectedOutputAmount: undefined, + }, + parameters: { + quoteRequest: mockQuoteRequestMinOutput, + }, +}; + +export const NoTokensSelected: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: undefined, + }, + parameters: { + quoteRequest: mockQuoteRequestNoTokens, + }, +}; + +export const LargeAmount: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: BigNumber.from("1234567890123"), + }, + parameters: { + quoteRequest: { + ...mockQuoteRequest, + amount: BigNumber.from("1234567890123"), + }, + }, +}; + +const mockQuoteRequestEmpty: QuoteRequest = { + tradeType: "exactInput", + originToken: mockInputToken, + destinationToken: mockOutputToken, + customDestinationAccount: null, + amount: null, +}; + +export const ThousandSeparatorTest: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: undefined, + }, + parameters: { + quoteRequest: mockQuoteRequestEmpty, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const originInput = canvas.getByTestId("bridge-amount-input"); + + await userEvent.clear(originInput); + await userEvent.type(originInput, "1234567.89", { delay: 50 }); + }, +}; diff --git a/src/views/SwapAndBridge/components/InputForm.tsx b/src/views/SwapAndBridge/components/InputForm.tsx index 683232c5c..30e2c1bdf 100644 --- a/src/views/SwapAndBridge/components/InputForm.tsx +++ b/src/views/SwapAndBridge/components/InputForm.tsx @@ -2,11 +2,11 @@ import { COLORS } from "utils"; import styled from "@emotion/styled"; import { useState } from "react"; import { ReactComponent as ArrowDown } from "assets/icons/arrow-down.svg"; -import { UnitType } from "hooks"; import { useQuoteRequestContext } from "../hooks/useQuoteRequest/QuoteRequestContext"; import { BigNumber } from "ethers"; import { OriginTokenInput } from "./TokenInput/OriginTokenInput"; import { DestinationTokenDisplay } from "./TokenInput/DestinationTokenDisplay"; +import { UnitType } from "../types"; export const InputForm = ({ isQuoteLoading, diff --git a/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx b/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx new file mode 100644 index 000000000..c369b5c96 --- /dev/null +++ b/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx @@ -0,0 +1,111 @@ +import { useCallback, useState } from "react"; +import { NumericFormat } from "react-number-format"; +import { BigNumber, utils } from "ethers"; +import { EnrichedToken } from "../ChainTokenSelector/ChainTokenSelectorModal"; +import { TokenAmountInput, TokenAmountInputWrapper } from "./styles"; +import { + convertTokenToUSD, + convertUSDToToken, + formatUnitsWithMaxFractions, +} from "../../../../utils"; +import { UnitType } from "../../types"; + +export function formatAmountForDisplay( + amount: BigNumber | null | undefined, + token: EnrichedToken | null, + unit: UnitType +): string { + if (!amount || !token) return ""; + if (unit === "token") { + return formatUnitsWithMaxFractions(amount, token.decimals); + } + const tokenAmountFormatted = formatUnitsWithMaxFractions( + amount, + token.decimals + ); + const usdValue = convertTokenToUSD(tokenAmountFormatted, token); + return utils.formatUnits(usdValue, 18); +} + +type AmountInputProps = { + id: string; + name: string; + testId?: string; + amount: BigNumber | null | undefined; + token: EnrichedToken | null; + unit: UnitType; + shouldUpdate: boolean; + disabled: boolean; + error: boolean; + setAmount: (amount: BigNumber | null) => void; + inputRef?: React.RefObject; +}; + +export const AmountInput = ({ + id, + name, + testId, + amount, + token, + unit, + shouldUpdate, + disabled, + error, + setAmount, + inputRef, +}: AmountInputProps) => { + const [isFocused, setIsFocused] = useState(false); + + const displayValue = formatAmountForDisplay(amount, token, unit); + + const handleValueChange = useCallback( + (value: number | undefined) => { + if (value === undefined || value <= 0) { + setAmount(null); + return; + } + if (!token) { + setAmount(null); + return; + } + if (unit === "token") { + const parsed = utils.parseUnits(value.toString(), token.decimals); + setAmount(parsed); + } else { + const tokenValue = convertUSDToToken(value.toString(), token); + setAmount(tokenValue); + } + }, + [setAmount, token, unit] + ); + + return ( + + { + if (sourceInfo.source === "event" && isFocused) { + handleValueChange(values.floatValue); + } + }} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + thousandSeparator="," + decimalSeparator="." + allowNegative={false} + disabled={disabled} + error={error} + /> + + ); +}; diff --git a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx index 94fcfab0d..75645269c 100644 --- a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx @@ -1,11 +1,8 @@ -import { UnitType, useTokenInput } from "hooks"; import { ChangeAccountModal } from "../ChangeAccountModal"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; import { BalanceSelector } from "../BalanceSelector"; import { - TokenAmountInput, TokenAmountInputTitle, - TokenAmountInputWrapper, TokenAmountStack, TokenInputWrapper, TokenSelectorColumn, @@ -13,6 +10,8 @@ import { import { useQuoteRequestContext } from "../../hooks/useQuoteRequest/QuoteRequestContext"; import { BigNumber } from "ethers"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; +import { AmountInput } from "./AmountInput"; +import { UnitType } from "../../types"; type DestinationTokenDisplayProps = { expectedOutputAmount: BigNumber | undefined; @@ -38,27 +37,15 @@ export const DestinationTokenDisplay = ({ const { destinationToken, originToken } = quoteRequest; - const { - amountString, - convertedAmount, - toggleUnit, - handleInputChange, - handleBalanceClick, - } = useTokenInput({ - token: destinationToken, - setAmount: setDestinationAmount, - expectedAmount: expectedOutputAmount, - shouldUpdate, - isUpdateLoading, - unit, - setUnit, - }); - const inputDisabled = (() => { if (!quoteRequest.destinationToken) return true; return Boolean(shouldUpdate && isUpdateLoading); })(); + const displayAmount = shouldUpdate + ? expectedOutputAmount + : quoteRequest.amount; + return ( @@ -67,26 +54,22 @@ export const DestinationTokenDisplay = ({ - - handleInputChange(e.target.value)} - disabled={inputDisabled} - error={false} - /> - + setAmount={setDestinationAmount} + /> @@ -104,11 +87,7 @@ export const DestinationTokenDisplay = ({ token={destinationToken} disableHover={true} error={false} - setAmount={(amount) => { - if (amount) { - handleBalanceClick(amount, destinationToken.decimals); - } - }} + setAmount={setDestinationAmount} /> )} diff --git a/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx b/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx index dc42ffb53..cd1ba353c 100644 --- a/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx @@ -1,11 +1,8 @@ import { useEffect, useRef } from "react"; -import { UnitType, useTokenInput } from "hooks"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; import { BalanceSelector } from "../BalanceSelector"; import { - TokenAmountInput, TokenAmountInputTitle, - TokenAmountInputWrapper, TokenAmountStack, TokenInputWrapper, TokenSelectorColumn, @@ -15,6 +12,8 @@ import { BigNumber } from "ethers"; import { hasInsufficientBalance } from "../../utils/balance"; import { useTokenBalance } from "views/SwapAndBridge/hooks/useTokenBalance"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; +import { AmountInput } from "./AmountInput"; +import { UnitType } from "../../types"; type OriginTokenInputProps = { expectedAmount: BigNumber | undefined; @@ -38,22 +37,6 @@ export const OriginTokenInput = ({ const shouldUpdate = quoteRequest.tradeType === "minOutput"; - const { - amountString, - convertedAmount, - toggleUnit, - handleInputChange, - handleBalanceClick, - } = useTokenInput({ - token: originToken, - setAmount: setOriginAmount, - expectedAmount, - shouldUpdate, - isUpdateLoading, - unit, - setUnit, - }); - const inputDisabled = (() => { if (!quoteRequest.destinationToken) return true; return Boolean(shouldUpdate && isUpdateLoading); @@ -78,33 +61,30 @@ export const OriginTokenInput = ({ } }, [inputDisabled]); + const displayAmount = shouldUpdate ? expectedAmount : quoteRequest.amount; + return ( From - - - handleInputChange(e.target.value)} - disabled={inputDisabled} - error={insufficientBalance} - /> - + setAmount={setOriginAmount} + inputRef={amountInputRef} + /> @@ -122,11 +102,7 @@ export const OriginTokenInput = ({ token={originToken} disableHover={false} error={insufficientBalance} - setAmount={(amount) => { - if (amount) { - handleBalanceClick(amount, originToken.decimals); - } - }} + setAmount={setOriginAmount} /> )} diff --git a/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx b/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx index c93b3c523..11d96dbc7 100644 --- a/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx @@ -1,25 +1,46 @@ -import { UnitType } from "../../../../../hooks"; +import { useCallback, useMemo } from "react"; import { BigNumber } from "ethers"; import { EnrichedToken } from "../../ChainTokenSelector/ChainTokenSelectorModal"; -import { formatUSD, withOpacity } from "../../../../../utils"; +import { + convertTokenToUSD, + convertUSDToToken, + formatUnitsWithMaxFractions, + formatUSD, + withOpacity, +} from "../../../../../utils"; import { formatUnits } from "ethers/lib/utils"; import styled from "@emotion/styled"; import { useTrackToggleUnit } from "./useTrackToggleUnit"; import { ReactComponent as ArrowsCross } from "assets/icons/arrows-cross.svg"; +import { UnitType } from "../../../types"; export function ToggleUnitButton({ - onClick, unit, - convertedAmount, + setUnit, + amount, token, }: { - onClick: () => void; unit: UnitType; - convertedAmount: BigNumber | undefined; + setUnit: (unit: UnitType) => void; + amount: BigNumber | null | undefined; token: EnrichedToken | null; }) { const trackToggleUnit = useTrackToggleUnit(); + const convertedAmount = useMemo(() => { + if (!amount || !token) return undefined; + const amountStr = formatUnitsWithMaxFractions(amount, token.decimals); + if (unit === "token") { + return convertTokenToUSD(amountStr, token); + } else { + return convertUSDToToken(amountStr, token); + } + }, [amount, token, unit]); + + const toggleUnit = useCallback(() => { + setUnit(unit === "token" ? "usd" : "token"); + }, [unit, setUnit]); + const formattedConvertedAmount = (() => { if (unit === "token") { if (!convertedAmount) return "$0.00"; @@ -34,7 +55,7 @@ export function ToggleUnitButton({ { trackToggleUnit(); - onClick(); + toggleUnit(); }} > {" "} diff --git a/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts b/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts index 901d15760..21bc177c1 100644 --- a/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts +++ b/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts @@ -3,8 +3,13 @@ import { quoteRequestReducer } from "./quoteRequestReducer"; import { QuoteRequest } from "./quoteRequestAction"; import { initialQuote } from "./initialQuote"; -const mockToken = (symbol: string) => - ({ symbol, chainId: 1, address: "0x" }) as QuoteRequest["originToken"]; +const mockToken = (symbol: string, decimals = 18) => + ({ + symbol, + chainId: 1, + address: "0x", + decimals, + }) as QuoteRequest["originToken"]; const mockAccount = (address: string) => ({ accountType: "evm", address }) as QuoteRequest["customDestinationAccount"]; @@ -104,6 +109,26 @@ describe("quoteRequestReducer", () => { }); expect(result.tradeType).toBe("exactInput"); }); + + it("preserves amount when swapping", () => { + const usdc1 = mockToken("USDC", 6); + const usdc2 = mockToken("USDC.e", 6); + + const state: QuoteRequest = { + ...initialQuote, + originToken: usdc1, + destinationToken: usdc2, + tradeType: "exactInput", + amount: BigNumber.from("10000000"), + }; + + const result = quoteRequestReducer(state, { + type: "QUICK_SWAP", + payload: undefined, + }); + + expect(result.amount?.toString()).toBe("10000000"); + }); }); it("returns previous state for unknown action", () => { diff --git a/src/views/SwapAndBridge/types.ts b/src/views/SwapAndBridge/types.ts new file mode 100644 index 000000000..f17595248 --- /dev/null +++ b/src/views/SwapAndBridge/types.ts @@ -0,0 +1 @@ +export type UnitType = "usd" | "token"; diff --git a/yarn.lock b/yarn.lock index ac0ffe6da..6c1c3ac5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23278,6 +23278,11 @@ react-modal@^3.12.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" +react-number-format@^5.4.4: + version "5.4.4" + resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad" + integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA== + react-pro-sidebar@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-pro-sidebar/-/react-pro-sidebar-1.1.0.tgz#e8f4ca0d7c4ff9fd2c38f8a0b85664083173b7ac"