diff --git a/src/components/common/TokenAmountInput/index.tsx b/src/components/common/TokenAmountInput/index.tsx index afad2dcc19..f1c81e9fdb 100644 --- a/src/components/common/TokenAmountInput/index.tsx +++ b/src/components/common/TokenAmountInput/index.tsx @@ -7,6 +7,7 @@ import { AutocompleteItem } from '@/components/tx-flow/flows/TokenTransfer/Creat import { useFormContext } from 'react-hook-form' import { type BigNumber } from '@ethersproject/bignumber' import classNames from 'classnames' +import { useCallback } from 'react' export enum TokenAmountFields { tokenAddress = 'tokenAddress', @@ -18,11 +19,13 @@ const TokenAmountInput = ({ selectedToken, onMaxAmountClick, maxAmount, + validate, }: { balances: SafeBalanceResponse['items'] selectedToken: SafeBalanceResponse['items'][number] | undefined - onMaxAmountClick: () => void - maxAmount: BigNumber + onMaxAmountClick?: () => void + maxAmount?: BigNumber + validate?: (value: string) => string | undefined }) => { const { formState: { errors }, @@ -34,6 +37,14 @@ const TokenAmountInput = ({ const tokenAddress = watch(TokenAmountFields.tokenAddress) const isAmountError = !!errors[TokenAmountFields.tokenAddress] || !!errors[TokenAmountFields.amount] + const validateAmount = useCallback( + (value: string) => { + const decimals = selectedToken?.tokenInfo.decimals + return validateLimitedAmount(value, decimals, maxAmount?.toString()) || validateDecimalLength(value, decimals) + }, + [maxAmount, selectedToken?.tokenInfo.decimals], + ) + return ( @@ -44,7 +55,7 @@ const TokenAmountInput = ({ variant="standard" InputProps={{ disableUnderline: true, - endAdornment: ( + endAdornment: onMaxAmountClick && ( @@ -55,10 +66,7 @@ const TokenAmountInput = ({ placeholder="0" {...register(TokenAmountFields.amount, { required: true, - validate: (val) => { - const decimals = selectedToken?.tokenInfo.decimals - return validateLimitedAmount(val, decimals, maxAmount.toString()) || validateDecimalLength(val, decimals) - }, + validate: validate ?? validateAmount, })} /> @@ -72,7 +80,7 @@ const TokenAmountInput = ({ {...register(TokenAmountFields.tokenAddress, { required: true, onChange: () => { - resetField(TokenAmountFields.amount, { defaultValue: '0' }) + resetField(TokenAmountFields.amount, { defaultValue: '' }) }, })} value={tokenAddress} diff --git a/src/components/tx-flow/flows/NewSpendingLimit/CreateSpendingLimit.tsx b/src/components/tx-flow/flows/NewSpendingLimit/CreateSpendingLimit.tsx index 90621c374d..fd0c23a4e0 100644 --- a/src/components/tx-flow/flows/NewSpendingLimit/CreateSpendingLimit.tsx +++ b/src/components/tx-flow/flows/NewSpendingLimit/CreateSpendingLimit.tsx @@ -1,6 +1,6 @@ -import { useMemo } from 'react' +import { useCallback, useMemo, useState } from 'react' import { Controller, FormProvider, useForm } from 'react-hook-form' -import { Button, CardActions, FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material' +import { Box, Button, CardActions, FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material' import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded' import { defaultAbiCoder, parseUnits } from 'ethers/lib/utils' @@ -12,9 +12,10 @@ import type { NewSpendingLimitFlowProps } from '.' import TxCard from '../../common/TxCard' import css from '@/components/tx/ExecuteCheckbox/styles.module.css' import TokenAmountInput from '@/components/common/TokenAmountInput' -import { BigNumber } from '@ethersproject/bignumber' -import { safeFormatUnits } from '@/utils/formatters' import { SpendingLimitFields } from '.' +import { validateAmount, validateDecimalLength } from '@/utils/validation' +import AddressInputReadOnly from '@/components/common/AddressInputReadOnly' +import useAddressBook from '@/hooks/useAddressBook' export const _validateSpendingLimit = (val: string, decimals?: number) => { // Allowance amount is uint96 https://github.com/safe-global/safe-modules/blob/master/allowances/contracts/AlowanceModule.sol#L52 @@ -33,8 +34,10 @@ export const CreateSpendingLimit = ({ params: NewSpendingLimitFlowProps onSubmit: (data: NewSpendingLimitFlowProps) => void }) => { + const [recipientFocus, setRecipientFocus] = useState(!params.beneficiary) const chainId = useChainId() const { balances } = useVisibleBalances() + const addressBook = useAddressBook() const resetTimeOptions = useMemo(() => getResetTimeOptions(chainId), [chainId]) @@ -45,37 +48,43 @@ export const CreateSpendingLimit = ({ const { handleSubmit, setValue, watch, control } = formMethods + const beneficiary = watch(SpendingLimitFields.beneficiary) const tokenAddress = watch(SpendingLimitFields.tokenAddress) const selectedToken = tokenAddress ? balances.items.find((item) => item.tokenInfo.address === tokenAddress) : undefined - const totalAmount = BigNumber.from(selectedToken?.balance || 0) - - const onMaxAmountClick = () => { - if (!selectedToken) return - - const amount = selectedToken.balance - - setValue(SpendingLimitFields.amount, safeFormatUnits(amount, selectedToken.tokenInfo.decimals), { - shouldValidate: true, - }) - } + const validateSpendingLimit = useCallback( + (value: string) => { + return ( + validateAmount(value) || + validateDecimalLength(value, selectedToken?.tokenInfo.decimals) || + _validateSpendingLimit(value, selectedToken?.tokenInfo.decimals) + ) + }, + [selectedToken?.tokenInfo.decimals], + ) return (
- + {addressBook[beneficiary] ? ( + { + setValue(SpendingLimitFields.beneficiary, '') + setRecipientFocus(true) + }} + > + + + ) : ( + + )} - + Reset Timer diff --git a/src/components/tx-flow/flows/NewSpendingLimit/ReviewSpendingLimit.tsx b/src/components/tx-flow/flows/NewSpendingLimit/ReviewSpendingLimit.tsx index 6341a96e5f..5ab7d1a2a2 100644 --- a/src/components/tx-flow/flows/NewSpendingLimit/ReviewSpendingLimit.tsx +++ b/src/components/tx-flow/flows/NewSpendingLimit/ReviewSpendingLimit.tsx @@ -12,7 +12,6 @@ import useChainId from '@/hooks/useChainId' import { trackEvent, SETTINGS_EVENTS } from '@/services/analytics' import { createNewSpendingLimitTx } from '@/services/tx/tx-sender' import { selectSpendingLimits } from '@/store/spendingLimitsSlice' -import { relativeTime } from '@/utils/date' import { formatVisualAmount } from '@/utils/formatters' import type { SpendingLimitState } from '@/store/spendingLimitsSlice' import type { NewSpendingLimitFlowProps } from '.' @@ -56,14 +55,22 @@ export const ReviewSpendingLimit = ({ params }: { params: NewSpendingLimitFlowPr }) } + const existingAmount = existingSpendingLimit + ? formatVisualAmount(BigNumber.from(existingSpendingLimit?.amount), decimals) + : undefined + + const oldResetTime = existingSpendingLimit + ? getResetTimeOptions(chainId).find((time) => time.value === existingSpendingLimit?.resetTimeMin)?.label + : undefined + return ( {token && ( - {!!existingSpendingLimit && ( + {existingAmount && existingAmount !== params.amount && ( <> - {formatVisualAmount(BigNumber.from(existingSpendingLimit.amount), decimals)} + {existingAmount} {'→'} @@ -109,7 +116,7 @@ export const ReviewSpendingLimit = ({ params }: { params: NewSpendingLimitFlowPr display="inline" component="span" > - {relativeTime(existingSpendingLimit.lastResetMin, existingSpendingLimit.resetTimeMin)} + {oldResetTime} {' → '} diff --git a/src/components/tx-flow/flows/RemoveGuard/index.tsx b/src/components/tx-flow/flows/RemoveGuard/index.tsx index a65ed8319c..2e96e6b471 100644 --- a/src/components/tx-flow/flows/RemoveGuard/index.tsx +++ b/src/components/tx-flow/flows/RemoveGuard/index.tsx @@ -8,7 +8,7 @@ export type RemoveGuardFlowProps = { const RemoveGuardFlow = ({ address }: RemoveGuardFlowProps) => { return ( - + )