From e5c76a6609381ae260e12323f86d462dc0d3cd59 Mon Sep 17 00:00:00 2001 From: Max Korsunov Date: Mon, 9 Sep 2024 12:44:56 +0500 Subject: [PATCH] fix(minifront): #1675: decimals validation (#1770) * fix(minifront): #1675: render decimals validation errors in swap * chore: changeset --- .changeset/three-suns-complain.md | 5 +++ .../src/components/shared/input-token.tsx | 8 ----- .../components/shared/number-input/index.tsx | 31 ++----------------- .../swap/swap-form/token-input-error.tsx | 23 ++++++++++++++ .../swap/swap-form/token-swap-input.tsx | 23 ++------------ apps/minifront/src/state/swap/index.ts | 23 +++++++++++++- 6 files changed, 56 insertions(+), 57 deletions(-) create mode 100644 .changeset/three-suns-complain.md create mode 100644 apps/minifront/src/components/swap/swap-form/token-input-error.tsx diff --git a/.changeset/three-suns-complain.md b/.changeset/three-suns-complain.md new file mode 100644 index 0000000000..9d455e5db8 --- /dev/null +++ b/.changeset/three-suns-complain.md @@ -0,0 +1,5 @@ +--- +'minifront': patch +--- + +Render the error in swap page in case of incorrect decimals or insufficient funds diff --git a/apps/minifront/src/components/shared/input-token.tsx b/apps/minifront/src/components/shared/input-token.tsx index 9e3232d8b7..a20659b195 100644 --- a/apps/minifront/src/components/shared/input-token.tsx +++ b/apps/minifront/src/components/shared/input-token.tsx @@ -1,7 +1,4 @@ -import { useMemo } from 'react'; import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; -import { getDisplayDenomExponent } from '@penumbra-zone/getters/metadata'; -import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; import { BalanceValueView } from '@penumbra-zone/ui/components/ui/balance-value-view'; import { cn } from '@penumbra-zone/ui/lib/utils'; import BalanceSelector from './selectors/balance-selector'; @@ -37,10 +34,6 @@ export default function InputToken({ onInputChange, loading, }: InputTokenProps) { - const tokenExponent = useMemo(() => { - return getDisplayDenomExponent.optional(getMetadataFromBalancesResponse.optional(selection)); - }, [selection]); - const setInputToBalanceMax = () => { const match = balances.find(b => b.balanceView?.equals(selection?.balanceView)); if (match?.balanceView) { @@ -55,7 +48,6 @@ export default function InputToken({ = ({ maxExponent, ...props }) => { +export const NumberInput: FC = props => { const inputRef = useWheelPrevent(); - const onKeyDown: KeyboardEventHandler = event => { - if (maxExponent === 0 && (event.key === '.' || event.key === ',')) { - event.preventDefault(); - return; - } - - if ( - typeof maxExponent !== 'undefined' && - typeof props.value === 'string' && - !Number.isNaN(Number(event.key)) - ) { - const fraction = `${props.value}${event.key}`.split('.')[1]?.length; - if (fraction && fraction > maxExponent) { - event.preventDefault(); - return; - } - } - props.onKeyDown?.(event); - }; - - return ; + return ; }; diff --git a/apps/minifront/src/components/swap/swap-form/token-input-error.tsx b/apps/minifront/src/components/swap/swap-form/token-input-error.tsx new file mode 100644 index 0000000000..b26d5c6fac --- /dev/null +++ b/apps/minifront/src/components/swap/swap-form/token-input-error.tsx @@ -0,0 +1,23 @@ +import { useStoreShallow } from '../../../utils/use-store-shallow.ts'; +import { swapErrorSelector } from '../../../state/swap'; + +export const TokenInputError = () => { + const { + incorrectDecimalErr, + amountMoreThanBalanceErr, + swappableAssetsError, + balanceResponsesError, + } = useStoreShallow(swapErrorSelector); + + const error = + amountMoreThanBalanceErr || + incorrectDecimalErr || + swappableAssetsError || + balanceResponsesError; + + if (!error) { + return null; + } + + return
{error}
; +}; diff --git a/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx b/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx index 8ad808d21e..208e8261c6 100644 --- a/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx +++ b/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx @@ -3,13 +3,8 @@ import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_ import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; import { Box } from '@penumbra-zone/ui/components/ui/box'; import { joinLoHiAmount } from '@penumbra-zone/types/amount'; -import { - getAmount, - getBalanceView, - getMetadataFromBalancesResponse, -} from '@penumbra-zone/getters/balances-response'; +import { getAmount, getBalanceView } from '@penumbra-zone/getters/balances-response'; import { ArrowRight } from 'lucide-react'; -import { useMemo } from 'react'; import { AllSlices } from '../../../state'; import { useStoreShallow } from '../../../utils/use-store-shallow'; import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view'; @@ -21,13 +16,12 @@ import { isValidAmount } from '../../../state/helpers'; import { NonNativeFeeWarning } from '../../shared/non-native-fee-warning'; import { NumberInput } from '../../shared/number-input'; import { useBalancesResponses, useAssets } from '../../../state/shared'; -import { FadeIn } from '@penumbra-zone/ui/components/ui/fade-in'; import { getBalanceByMatchingMetadataAndAddressIndex } from '../../../state/swap/getters'; import { swappableAssetsSelector, swappableBalancesResponsesSelector, } from '../../../state/swap/helpers'; -import { getDisplayDenomExponent } from '@penumbra-zone/getters/metadata'; +import { TokenInputError } from './token-input-error.tsx'; const getAssetOutBalance = ( balancesResponses: BalancesResponse[] = [], @@ -69,9 +63,6 @@ export const TokenSwapInput = () => { const { amount, setAmount, assetIn, setAssetIn, assetOut, setAssetOut, reverse } = useStoreShallow(tokenSwapInputSelector); const assetOutBalance = getAssetOutBalance(balancesResponses?.data, assetIn, assetOut); - const assetInExponent = useMemo(() => { - return getDisplayDenomExponent.optional(getMetadataFromBalancesResponse.optional(assetIn)); - }, [assetIn]); const maxAmount = getAmount.optional(assetIn); const maxAmountAsString = maxAmount ? joinLoHiAmount(maxAmount).toString() : undefined; @@ -84,7 +75,7 @@ export const TokenSwapInput = () => { }; return ( - + }>
{ variant='transparent' placeholder='Enter an amount...' max={maxAmountAsString} - maxExponent={assetInExponent} step='any' className={'font-bold leading-10 md:h-8 md:text-xl xl:h-10 xl:text-3xl'} onChange={e => { @@ -108,13 +98,6 @@ export const TokenSwapInput = () => {
)} - -
- {balancesResponses?.error instanceof Error && balancesResponses.error.toString()} - {swappableAssets?.error instanceof Error && swappableAssets.error.toString()} -
-
-
; @@ -144,6 +145,26 @@ export const createSwapSlice = (): SliceCreator => (set, get, store) }, }); +export const swapErrorSelector = (state: AllSlices) => ({ + balanceResponsesError: + state.shared.balancesResponses.error instanceof Error && + state.shared.balancesResponses.error.toString(), + swappableAssetsError: + state.shared.assets.error instanceof Error && state.shared.assets.error.toString(), + amountMoreThanBalanceErr: + state.swap.amount && + state.swap.assetIn && + amountMoreThanBalance(state.swap.assetIn, state.swap.amount) + ? 'Insufficient funds' + : '', + incorrectDecimalErr: + state.swap.amount && + state.swap.assetIn && + isIncorrectDecimal(state.swap.assetIn, state.swap.amount) + ? `Incorrect decimals, maximum ${getDisplayDenomExponent.optional(getMetadataFromBalancesResponse.optional(state.swap.assetIn))} allowed` + : '', +}); + export const submitButtonDisabledSelector = (state: AllSlices) => !state.swap.amount || !isValidAmount(state.swap.amount, state.swap.assetIn) ||