bridge_ui: max button for number inputs

Change-Id: I4fdbb2b3191a012cbdf02df4ad6292c78eed5bb7
This commit is contained in:
Evan Gray 2021-10-21 17:11:03 -04:00
parent 5ecdde62e1
commit cff4d928b6
4 changed files with 64 additions and 20 deletions

View File

@ -1,10 +1,5 @@
import { ChainId } from "@certusone/wormhole-sdk"; import { ChainId } from "@certusone/wormhole-sdk";
import { import { CircularProgress, makeStyles, Typography } from "@material-ui/core";
CircularProgress,
makeStyles,
TextField,
Typography,
} from "@material-ui/core";
import { Alert } from "@material-ui/lab"; import { Alert } from "@material-ui/lab";
import { parseUnits } from "ethers/lib/utils"; import { parseUnits } from "ethers/lib/utils";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
@ -14,6 +9,7 @@ import useEthereumMigratorInformation from "../../hooks/useEthereumMigratorInfor
import useIsWalletReady from "../../hooks/useIsWalletReady"; import useIsWalletReady from "../../hooks/useIsWalletReady";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import EthereumSignerKey from "../EthereumSignerKey"; import EthereumSignerKey from "../EthereumSignerKey";
import NumberTextField from "../NumberTextField";
import ShowTx from "../ShowTx"; import ShowTx from "../ShowTx";
import SmartAddress from "../SmartAddress"; import SmartAddress from "../SmartAddress";
@ -49,6 +45,7 @@ export default function EvmWorkflow({
signerAddress, signerAddress,
toggleRefresh toggleRefresh
); );
const fromWalletBalance = poolInfo.data?.fromWalletBalance;
const [migrationAmount, setMigrationAmount] = useState(""); const [migrationAmount, setMigrationAmount] = useState("");
const [migrationIsProcessing, setMigrationIsProcessing] = useState(false); const [migrationIsProcessing, setMigrationIsProcessing] = useState(false);
@ -69,9 +66,9 @@ export default function EvmWorkflow({
const hasRequisiteData = poolInfo.data; const hasRequisiteData = poolInfo.data;
const amountGreaterThanZero = fromParse(migrationAmount) > BigInt(0); const amountGreaterThanZero = fromParse(migrationAmount) > BigInt(0);
const sufficientFromTokens = const sufficientFromTokens =
poolInfo.data?.fromWalletBalance && fromWalletBalance &&
migrationAmount && migrationAmount &&
fromParse(migrationAmount) <= fromParse(poolInfo.data.fromWalletBalance); fromParse(migrationAmount) <= fromParse(fromWalletBalance);
const sufficientPoolBalance = const sufficientPoolBalance =
poolInfo.data?.toPoolBalance && poolInfo.data?.toPoolBalance &&
migrationAmount && migrationAmount &&
@ -106,6 +103,11 @@ export default function EvmWorkflow({
(event) => setMigrationAmount(event.target.value), (event) => setMigrationAmount(event.target.value),
[setMigrationAmount] [setMigrationAmount]
); );
const handleMaxClick = useCallback(() => {
if (fromWalletBalance) {
setMigrationAmount(fromWalletBalance);
}
}, [fromWalletBalance]);
const migrateTokens = useCallback(async () => { const migrateTokens = useCallback(async () => {
if (!poolInfo.data) { if (!poolInfo.data) {
@ -170,8 +172,7 @@ export default function EvmWorkflow({
<div> <div>
<Typography>This action will convert</Typography> <Typography>This action will convert</Typography>
<Typography variant="h6"> <Typography variant="h6">
{fromTokenPretty}{" "} {fromTokenPretty} {`(Balance: ${fromWalletBalance || ""})`}
{`(Balance: ${poolInfo.data?.fromWalletBalance || ""})`}
</Typography> </Typography>
<div className={classes.spacer} /> <div className={classes.spacer} />
<Typography>to</Typography> <Typography>to</Typography>
@ -190,14 +191,14 @@ export default function EvmWorkflow({
<> <>
{explainerContent} {explainerContent}
<div className={classes.spacer} /> <div className={classes.spacer} />
<TextField <NumberTextField
variant="outlined" variant="outlined"
value={migrationAmount} value={migrationAmount}
type="number"
onChange={handleAmountChange} onChange={handleAmountChange}
label={"Amount"} label={"Amount"}
disabled={!!migrationIsProcessing || !!transaction} disabled={!!migrationIsProcessing || !!transaction}
></TextField> onMaxClick={fromWalletBalance ? handleMaxClick : undefined}
/>
{!transaction && ( {!transaction && (
<ButtonWithLoader <ButtonWithLoader

View File

@ -2,7 +2,7 @@ import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
import migrateTokensTx from "@certusone/wormhole-sdk/lib/migration/migrateTokens"; import migrateTokensTx from "@certusone/wormhole-sdk/lib/migration/migrateTokens";
import getPoolAddress from "@certusone/wormhole-sdk/lib/migration/poolAddress"; import getPoolAddress from "@certusone/wormhole-sdk/lib/migration/poolAddress";
import getToCustodyAddress from "@certusone/wormhole-sdk/lib/migration/toCustodyAddress"; import getToCustodyAddress from "@certusone/wormhole-sdk/lib/migration/toCustodyAddress";
import { makeStyles, TextField, Typography } from "@material-ui/core"; import { makeStyles, Typography } from "@material-ui/core";
import { import {
ASSOCIATED_TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID,
Token, Token,
@ -19,6 +19,7 @@ import { COLORS } from "../../muiTheme";
import { MIGRATION_PROGRAM_ADDRESS, SOLANA_HOST } from "../../utils/consts"; import { MIGRATION_PROGRAM_ADDRESS, SOLANA_HOST } from "../../utils/consts";
import { getMultipleAccounts, signSendAndConfirm } from "../../utils/solana"; import { getMultipleAccounts, signSendAndConfirm } from "../../utils/solana";
import ButtonWithLoader from "../ButtonWithLoader"; import ButtonWithLoader from "../ButtonWithLoader";
import NumberTextField from "../NumberTextField";
import ShowTx from "../ShowTx"; import ShowTx from "../ShowTx";
import SmartAddress from "../SmartAddress"; import SmartAddress from "../SmartAddress";
import SolanaCreateAssociatedAddress, { import SolanaCreateAssociatedAddress, {
@ -358,6 +359,11 @@ export default function Workflow({
(event) => setMigrationAmount(event.target.value), (event) => setMigrationAmount(event.target.value),
[setMigrationAmount] [setMigrationAmount]
); );
const handleMaxClick = useCallback(() => {
if (fromTokenAccountBalance) {
setMigrationAmount(fromTokenAccountBalance);
}
}, [fromTokenAccountBalance]);
const getMetadata = (address: string) => { const getMetadata = (address: string) => {
const tokenMapItem = solanaTokenMap.data?.find( const tokenMapItem = solanaTokenMap.data?.find(
@ -459,14 +465,14 @@ export default function Workflow({
</> </>
) : null} ) : null}
<div className={classes.spacer} /> <div className={classes.spacer} />
<TextField <NumberTextField
variant="outlined" variant="outlined"
value={migrationAmount} value={migrationAmount}
type="number"
onChange={handleAmountChange} onChange={handleAmountChange}
label={"Amount"} label={"Amount"}
disabled={!!migrationIsProcessing || !!transaction} disabled={!!migrationIsProcessing || !!transaction}
></TextField> onMaxClick={fromTokenAccountBalance ? handleMaxClick : undefined}
/>
{!transaction && ( {!transaction && (
<ButtonWithLoader <ButtonWithLoader

View File

@ -0,0 +1,31 @@
import {
Button,
InputAdornment,
TextField,
TextFieldProps,
} from "@material-ui/core";
export default function NumberTextField(
props: TextFieldProps & { onMaxClick?: () => void }
) {
return (
<TextField
type="number"
{...props}
InputProps={{
endAdornment: props.onMaxClick ? (
<InputAdornment position="end">
<Button
onClick={props.onMaxClick}
disabled={props.disabled}
variant="outlined"
>
Max
</Button>
</InputAdornment>
) : undefined,
...(props?.InputProps || {}),
}}
></TextField>
);
}

View File

@ -4,7 +4,7 @@ import {
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { getAddress } from "@ethersproject/address"; import { getAddress } from "@ethersproject/address";
import { Button, makeStyles, TextField } from "@material-ui/core"; import { Button, makeStyles } from "@material-ui/core";
import { useCallback } from "react"; import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router"; import { useHistory } from "react-router";
@ -33,6 +33,7 @@ import ButtonWithLoader from "../ButtonWithLoader";
import ChainSelect from "../ChainSelect"; import ChainSelect from "../ChainSelect";
import KeyAndBalance from "../KeyAndBalance"; import KeyAndBalance from "../KeyAndBalance";
import LowBalanceWarning from "../LowBalanceWarning"; import LowBalanceWarning from "../LowBalanceWarning";
import NumberTextField from "../NumberTextField";
import StepDescription from "../StepDescription"; import StepDescription from "../StepDescription";
import { TokenSelector } from "../TokenSelectors/SourceTokenSelector"; import { TokenSelector } from "../TokenSelectors/SourceTokenSelector";
import TokenWarning from "./TokenWarning"; import TokenWarning from "./TokenWarning";
@ -95,6 +96,11 @@ function Source() {
}, },
[dispatch] [dispatch]
); );
const handleMaxClick = useCallback(() => {
if (uiAmountString) {
dispatch(setAmount(uiAmountString));
}
}, [dispatch, uiAmountString]);
const handleNextClick = useCallback(() => { const handleNextClick = useCallback(() => {
dispatch(incrementStep()); dispatch(incrementStep());
}, [dispatch]); }, [dispatch]);
@ -136,15 +142,15 @@ function Source() {
/> />
<LowBalanceWarning chainId={sourceChain} /> <LowBalanceWarning chainId={sourceChain} />
{hasParsedTokenAccount ? ( {hasParsedTokenAccount ? (
<TextField <NumberTextField
variant="outlined" variant="outlined"
label="Amount" label="Amount"
type="number"
fullWidth fullWidth
className={classes.transferField} className={classes.transferField}
value={amount} value={amount}
onChange={handleAmountChange} onChange={handleAmountChange}
disabled={shouldLockFields} disabled={shouldLockFields}
onMaxClick={uiAmountString ? handleMaxClick : undefined}
/> />
) : null} ) : null}
<ButtonWithLoader <ButtonWithLoader