From 42bd97057c25f42d03b16996aaffc66cf2703866 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Thu, 5 Oct 2023 12:20:36 +0200 Subject: [PATCH 1/7] Improve staking form user experience Added validation if user wallet is empty, simplified and made validation error messages more descriptive, improved readability of validation function, disabled form if wallet is not connected. --- src/components/Forms/TokenAmountForm.tsx | 18 +++++++------ src/pages/Staking/NewStakeCard.tsx | 4 +++ src/utils/forms.ts | 33 ++++++++++++++---------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/components/Forms/TokenAmountForm.tsx b/src/components/Forms/TokenAmountForm.tsx index 7b6e5b4ec..4b5e24dbe 100644 --- a/src/components/Forms/TokenAmountForm.tsx +++ b/src/components/Forms/TokenAmountForm.tsx @@ -1,16 +1,16 @@ +import { BodySm, Box, ButtonProps, Icon } from "@threshold-network/components" +import { FormikErrors, FormikProps, withFormik } from "formik" import { FC, Ref } from "react" -import { Icon, Box, ButtonProps, BodySm } from "@threshold-network/components" -import { withFormik, FormikProps, FormikErrors } from "formik" import ThresholdCircleBrand from "../../static/icons/ThresholdCircleBrand" -import { FormikTokenBalanceInput } from "./FormikTokenBalanceInput" -import SubmitTxButton from "../SubmitTxButton" -import { Form } from "./Form" +import { formatTokenAmount } from "../../utils/formatAmount" import { DEFAULT_MIN_VALUE, getErrorsObj, validateAmountInRange, } from "../../utils/forms" -import { formatTokenAmount } from "../../utils/formatAmount" +import SubmitTxButton from "../SubmitTxButton" +import { Form } from "./Form" +import { FormikTokenBalanceInput } from "./FormikTokenBalanceInput" export type FormValues = { tokenAmount: string @@ -25,6 +25,7 @@ export type TokenAmountFormBaseProps = { isDisabled?: boolean shouldValidateForm?: boolean shouldDisplayMaxAmountInLabel?: boolean + shouldDisableButton?: boolean token?: { decimals: number; symbol: string } placeholder?: string minTokenAmount?: string | number @@ -43,6 +44,7 @@ export const TokenAmountFormBase: FC< isDisabled = false, shouldValidateForm = true, shouldDisplayMaxAmountInLabel = false, + shouldDisableButton = true, placeholder, submitButtonVariant = "solid", ...formikProps @@ -71,7 +73,7 @@ export const TokenAmountFormBase: FC< max={maxTokenAmount} helperText={helperText} isDisabled={isDisabled} - _disabled={{ bg: "gray.50", border: "none" }} + _disabled={{ bg: "gray.50", border: "none", cursor: "not-allowed" }} /> ) diff --git a/src/pages/Staking/NewStakeCard.tsx b/src/pages/Staking/NewStakeCard.tsx index 8784b9993..ed6ea8402 100644 --- a/src/pages/Staking/NewStakeCard.tsx +++ b/src/pages/Staking/NewStakeCard.tsx @@ -7,11 +7,13 @@ import { useMinStakeAmount } from "../../hooks/useMinStakeAmount" import { useModal } from "../../hooks/useModal" import { useTokenBalance } from "../../hooks/useTokenBalance" import { formatTokenAmount } from "../../utils/formatAmount" +import { useWeb3React } from "@web3-react/core" const NewStakeCard: FC> = () => { const { openModal } = useModal() const tBalance = useTokenBalance(Token.T) const { minStakeAmount, isLoading, hasError } = useMinStakeAmount() + const { active: isWalletConnected } = useWeb3React() const openStakingModal = async (stakeAmount: string) => { openModal(ModalType.StakingChecklist, { stakeAmount }) @@ -39,6 +41,8 @@ const NewStakeCard: FC> = () => { maxTokenAmount={tBalance} placeholder={placeholder} minTokenAmount={minStakeAmount} + shouldDisableButton={false} + isDisabled={!isWalletConnected} /> diff --git a/src/utils/forms.ts b/src/utils/forms.ts index 25496ff71..24d162a9f 100644 --- a/src/utils/forms.ts +++ b/src/utils/forms.ts @@ -15,28 +15,26 @@ type ValidationOptions = { greaterThanValidationMsg: ValidationMsg lessThanValidationMsg: ValidationMsg requiredMsg: string + insufficientBalanceMsg: string } export const DEFAULT_MIN_VALUE = WeiPerEther.toString() -export const defaultLessThanMsg: (minAmount: string) => string = ( - minAmount +export const defaultLessThanMsg: (maxAmount: string) => string = ( + maxAmount ) => { - return `The value should be less than or equal ${formatTokenAmount( - minAmount - )}` + return `The maximum stake amount is ${formatTokenAmount(maxAmount)}.` } export const defaultGreaterThanMsg: (minAmount: string) => string = ( - maxAmount + minAmount ) => { - return `The value should be greater than or equal ${formatTokenAmount( - maxAmount - )}` + return `The minimum stake amount is ${formatTokenAmount(minAmount)}.` } export const defaultValidationOptions: ValidationOptions = { greaterThanValidationMsg: defaultGreaterThanMsg, lessThanValidationMsg: defaultLessThanMsg, - requiredMsg: "Required", + requiredMsg: "The stake amount is required.", + insufficientBalanceMsg: "Your wallet balance is insufficient.", } export const validateAmountInRange = ( @@ -45,7 +43,8 @@ export const validateAmountInRange = ( minValue = DEFAULT_MIN_VALUE, options: ValidationOptions = defaultValidationOptions ) => { - if (!value) { + const isValueMissing = !value + if (isValueMissing) { return options.requiredMsg } @@ -53,11 +52,19 @@ export const validateAmountInRange = ( const maxValueInBN = BigNumber.from(maxValue) const minValueInBN = BigNumber.from(minValue) - if (valueInBN.gt(maxValueInBN)) { + const isBalanceInsufficient = maxValueInBN.isZero() + const isMaximumValueExceeded = valueInBN.gt(maxValueInBN) + const isMinimumValueFulfilled = valueInBN.gte(minValueInBN) + + if (isMinimumValueFulfilled && isBalanceInsufficient) { + return options.insufficientBalanceMsg + } + if (!isBalanceInsufficient && isMaximumValueExceeded) { return typeof options.lessThanValidationMsg === "function" ? options.lessThanValidationMsg(maxValue) : options.lessThanValidationMsg - } else if (valueInBN.lt(minValueInBN)) { + } + if (!isMinimumValueFulfilled) { return typeof options.greaterThanValidationMsg === "function" ? options.greaterThanValidationMsg(minValue) : options.greaterThanValidationMsg From 586f52402578b15e91459e62b6d3c37c01ae8783 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Mon, 23 Oct 2023 09:58:19 +0200 Subject: [PATCH 2/7] Rename variables Renamed variables: defaultLessThanMsg -> defaultStakingLessThanMessage defaultGreaterThanMsg -> defaultStakingGreaterThanMessage defaultValidationOptions -> defaultStakingValidationOptions ValidationMsg -> AmountValidationMessage ValidationOptions -> AmountValidationOptions --- .../StakingApplicationForms/index.tsx | 30 ++++++------- src/utils/forms.ts | 44 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/components/StakingApplicationForms/index.tsx b/src/components/StakingApplicationForms/index.tsx index d8111ba78..0e933f610 100644 --- a/src/components/StakingApplicationForms/index.tsx +++ b/src/components/StakingApplicationForms/index.tsx @@ -1,20 +1,20 @@ -import { FC, Ref, useEffect, useState } from "react" +import { BodyMd, BodySm } from "@threshold-network/components" import { BigNumber } from "ethers" import { FormikErrors, FormikProps, withFormik } from "formik" -import { BodyMd, BodySm } from "@threshold-network/components" -import { - TokenAmountFormBase, - TokenAmountFormBaseProps, - FormValues, -} from "../Forms" +import { FC, Ref, useEffect, useState } from "react" +import { formatTokenAmount } from "../../utils/formatAmount" import { - defaultLessThanMsg, - defaultValidationOptions, + defaultStakingLessThanMessage, + defaultAmountValidationOptions, DEFAULT_MIN_VALUE, getErrorsObj, validateAmountInRange, } from "../../utils/forms" -import { formatTokenAmount } from "../../utils/formatAmount" +import { + FormValues, + TokenAmountFormBase, + TokenAmountFormBaseProps, +} from "../Forms" type ComponentProps = { totalStake: string @@ -148,11 +148,11 @@ const deauthorizationValidation = ( max.toString(), DEFAULT_MIN_VALUE, { - ...defaultValidationOptions, - lessThanValidationMsg(amount) { - return `${defaultLessThanMsg(amount)} or equal to ${formatTokenAmount( - authorizedAmount.toString() - )} T` + ...defaultAmountValidationOptions, + lessThanValidationMessage(amount) { + return `${defaultStakingLessThanMessage( + amount + )} or equal to ${formatTokenAmount(authorizedAmount.toString())} T` }, } ) diff --git a/src/utils/forms.ts b/src/utils/forms.ts index 24d162a9f..d1980fc3b 100644 --- a/src/utils/forms.ts +++ b/src/utils/forms.ts @@ -10,42 +10,42 @@ import { isAddress, isAddressZero } from "../web3/utils" import { formatTokenAmount } from "./formatAmount" import { getBridgeBTCSupportedAddressPrefixesText } from "./tBTC" -type ValidationMsg = string | ((amount: string) => string) -type ValidationOptions = { - greaterThanValidationMsg: ValidationMsg - lessThanValidationMsg: ValidationMsg - requiredMsg: string - insufficientBalanceMsg: string +type AmountValidationMessage = string | ((amount: string) => string) +type AmountValidationOptions = { + greaterThanValidationMessage: AmountValidationMessage + lessThanValidationMessage: AmountValidationMessage + requiredMessage: string + insufficientBalanceMessage: string } export const DEFAULT_MIN_VALUE = WeiPerEther.toString() -export const defaultLessThanMsg: (maxAmount: string) => string = ( +export const defaultStakingLessThanMessage: (maxAmount: string) => string = ( maxAmount ) => { return `The maximum stake amount is ${formatTokenAmount(maxAmount)}.` } -export const defaultGreaterThanMsg: (minAmount: string) => string = ( +export const defaultStakingGreaterThanMessage: (minAmount: string) => string = ( minAmount ) => { return `The minimum stake amount is ${formatTokenAmount(minAmount)}.` } -export const defaultValidationOptions: ValidationOptions = { - greaterThanValidationMsg: defaultGreaterThanMsg, - lessThanValidationMsg: defaultLessThanMsg, - requiredMsg: "The stake amount is required.", - insufficientBalanceMsg: "Your wallet balance is insufficient.", +export const defaultAmountValidationOptions: AmountValidationOptions = { + greaterThanValidationMessage: defaultStakingGreaterThanMessage, + lessThanValidationMessage: defaultStakingLessThanMessage, + requiredMessage: "The stake amount is required.", + insufficientBalanceMessage: "Your wallet balance is insufficient.", } export const validateAmountInRange = ( value: string, maxValue: string, minValue = DEFAULT_MIN_VALUE, - options: ValidationOptions = defaultValidationOptions + options: AmountValidationOptions = defaultAmountValidationOptions ) => { const isValueMissing = !value if (isValueMissing) { - return options.requiredMsg + return options.requiredMessage } const valueInBN = BigNumber.from(value) @@ -57,17 +57,17 @@ export const validateAmountInRange = ( const isMinimumValueFulfilled = valueInBN.gte(minValueInBN) if (isMinimumValueFulfilled && isBalanceInsufficient) { - return options.insufficientBalanceMsg + return options.insufficientBalanceMessage } if (!isBalanceInsufficient && isMaximumValueExceeded) { - return typeof options.lessThanValidationMsg === "function" - ? options.lessThanValidationMsg(maxValue) - : options.lessThanValidationMsg + return typeof options.lessThanValidationMessage === "function" + ? options.lessThanValidationMessage(maxValue) + : options.lessThanValidationMessage } if (!isMinimumValueFulfilled) { - return typeof options.greaterThanValidationMsg === "function" - ? options.greaterThanValidationMsg(minValue) - : options.greaterThanValidationMsg + return typeof options.greaterThanValidationMessage === "function" + ? options.greaterThanValidationMessage(minValue) + : options.greaterThanValidationMessage } } From 9ad18d04a46162e7e911d725ef7fc3376e17177d Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Mon, 23 Oct 2023 10:24:21 +0200 Subject: [PATCH 3/7] Refactor validateAmountRange conditional returns Applied refinemets, added getAmountInRangeValidationMessage function --- src/utils/forms.ts | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/utils/forms.ts b/src/utils/forms.ts index d1980fc3b..08083ddba 100644 --- a/src/utils/forms.ts +++ b/src/utils/forms.ts @@ -37,6 +37,15 @@ export const defaultAmountValidationOptions: AmountValidationOptions = { insufficientBalanceMessage: "Your wallet balance is insufficient.", } +const getAmountInRangeValidationMessage = ( + validationMessage: AmountValidationMessage, + value: string +) => { + return typeof validationMessage === "function" + ? validationMessage(value) + : validationMessage +} + export const validateAmountInRange = ( value: string, maxValue: string, @@ -56,18 +65,21 @@ export const validateAmountInRange = ( const isMaximumValueExceeded = valueInBN.gt(maxValueInBN) const isMinimumValueFulfilled = valueInBN.gte(minValueInBN) - if (isMinimumValueFulfilled && isBalanceInsufficient) { - return options.insufficientBalanceMessage + if (!isMinimumValueFulfilled) { + return getAmountInRangeValidationMessage( + options.greaterThanValidationMessage, + minValue + ) } - if (!isBalanceInsufficient && isMaximumValueExceeded) { - return typeof options.lessThanValidationMessage === "function" - ? options.lessThanValidationMessage(maxValue) - : options.lessThanValidationMessage + if (isBalanceInsufficient) { + return options.insufficientBalanceMessage } - if (!isMinimumValueFulfilled) { - return typeof options.greaterThanValidationMessage === "function" - ? options.greaterThanValidationMessage(minValue) - : options.greaterThanValidationMessage + + if (isMaximumValueExceeded) { + return getAmountInRangeValidationMessage( + options.lessThanValidationMessage, + maxValue + ) } } From ec07679ad45778d0453f06058af6d1a1ed4f63e2 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Mon, 23 Oct 2023 11:11:16 +0200 Subject: [PATCH 4/7] Remove isValueMissing variable --- src/utils/forms.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/forms.ts b/src/utils/forms.ts index 08083ddba..ae1d395b5 100644 --- a/src/utils/forms.ts +++ b/src/utils/forms.ts @@ -52,8 +52,7 @@ export const validateAmountInRange = ( minValue = DEFAULT_MIN_VALUE, options: AmountValidationOptions = defaultAmountValidationOptions ) => { - const isValueMissing = !value - if (isValueMissing) { + if (!value) { return options.requiredMessage } From 803f003cf9d14c3521bbcf070415ec25e77bcce1 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 21 Nov 2023 17:48:42 +0100 Subject: [PATCH 5/7] Change back the default error messages In 42bd97057c25f42d03b16996aaffc66cf2703866 we've changed the default messages in a way that they are related to Token staking. This is bad because now such messages were displayed in tbtc section (for example). This commit changes the default error message back to where it was before this commit. --- .../StakingApplicationForms/index.tsx | 4 ++-- src/utils/forms.ts | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/StakingApplicationForms/index.tsx b/src/components/StakingApplicationForms/index.tsx index 0e933f610..0b45eebbe 100644 --- a/src/components/StakingApplicationForms/index.tsx +++ b/src/components/StakingApplicationForms/index.tsx @@ -4,7 +4,7 @@ import { FormikErrors, FormikProps, withFormik } from "formik" import { FC, Ref, useEffect, useState } from "react" import { formatTokenAmount } from "../../utils/formatAmount" import { - defaultStakingLessThanMessage, + defaultLessThanMessage, defaultAmountValidationOptions, DEFAULT_MIN_VALUE, getErrorsObj, @@ -150,7 +150,7 @@ const deauthorizationValidation = ( { ...defaultAmountValidationOptions, lessThanValidationMessage(amount) { - return `${defaultStakingLessThanMessage( + return `${defaultLessThanMessage( amount )} or equal to ${formatTokenAmount(authorizedAmount.toString())} T` }, diff --git a/src/utils/forms.ts b/src/utils/forms.ts index ae1d395b5..c607616ed 100644 --- a/src/utils/forms.ts +++ b/src/utils/forms.ts @@ -19,21 +19,25 @@ type AmountValidationOptions = { } export const DEFAULT_MIN_VALUE = WeiPerEther.toString() -export const defaultStakingLessThanMessage: (maxAmount: string) => string = ( +export const defaultLessThanMessage: (maxAmount: string) => string = ( maxAmount ) => { - return `The maximum stake amount is ${formatTokenAmount(maxAmount)}.` + return `The value should be less than or equal ${formatTokenAmount( + maxAmount + )}` } -export const defaultStakingGreaterThanMessage: (minAmount: string) => string = ( +export const defaultGreaterThanMessage: (minAmount: string) => string = ( minAmount ) => { - return `The minimum stake amount is ${formatTokenAmount(minAmount)}.` + return `The value should be greater than or equal ${formatTokenAmount( + minAmount + )}` } export const defaultAmountValidationOptions: AmountValidationOptions = { - greaterThanValidationMessage: defaultStakingGreaterThanMessage, - lessThanValidationMessage: defaultStakingLessThanMessage, - requiredMessage: "The stake amount is required.", + greaterThanValidationMessage: defaultGreaterThanMessage, + lessThanValidationMessage: defaultLessThanMessage, + requiredMessage: "Required.", insufficientBalanceMessage: "Your wallet balance is insufficient.", } From ed3ae670abc6efd3a4090535432f8d3debeb2cc5 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 21 Nov 2023 17:51:57 +0100 Subject: [PATCH 6/7] Remove isWalletConnected property The property name change is not needed because we usually use default `active` property from `useWeb3React` hook anyway. --- src/pages/Staking/NewStakeCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Staking/NewStakeCard.tsx b/src/pages/Staking/NewStakeCard.tsx index ed6ea8402..a28ac3d5b 100644 --- a/src/pages/Staking/NewStakeCard.tsx +++ b/src/pages/Staking/NewStakeCard.tsx @@ -13,7 +13,7 @@ const NewStakeCard: FC> = () => { const { openModal } = useModal() const tBalance = useTokenBalance(Token.T) const { minStakeAmount, isLoading, hasError } = useMinStakeAmount() - const { active: isWalletConnected } = useWeb3React() + const { active } = useWeb3React() const openStakingModal = async (stakeAmount: string) => { openModal(ModalType.StakingChecklist, { stakeAmount }) @@ -42,7 +42,7 @@ const NewStakeCard: FC> = () => { placeholder={placeholder} minTokenAmount={minStakeAmount} shouldDisableButton={false} - isDisabled={!isWalletConnected} + isDisabled={!active} /> From bb143c48034b30f9fae4db7d5934b8a4dddee857 Mon Sep 17 00:00:00 2001 From: michalsmiarowski Date: Tue, 21 Nov 2023 17:55:30 +0100 Subject: [PATCH 7/7] Refactor disabled button for token amount form The previosu commit introduces `shouldDisableButton` property which might get a little confusing, because when it's set to true someone might expect that the button is disabled no matter what, when in reality it relies on `isDisabled` property also. Instead of this, I've added `isDisabled` property for `Connect Wallet` button in SubmitTxButton component and set it to false. This overrides the `isDisabled` property from `...buttonProp`, because in this case we don't want ot have this button disabled anyway. This means that we can use `isDisabled` to disable token amount form if the account is not connected but keep the `Connect Wallet` button active. --- src/components/Forms/TokenAmountForm.tsx | 4 +--- src/components/SubmitTxButton.tsx | 1 + src/pages/Staking/NewStakeCard.tsx | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/Forms/TokenAmountForm.tsx b/src/components/Forms/TokenAmountForm.tsx index 4b5e24dbe..bcc8a9404 100644 --- a/src/components/Forms/TokenAmountForm.tsx +++ b/src/components/Forms/TokenAmountForm.tsx @@ -25,7 +25,6 @@ export type TokenAmountFormBaseProps = { isDisabled?: boolean shouldValidateForm?: boolean shouldDisplayMaxAmountInLabel?: boolean - shouldDisableButton?: boolean token?: { decimals: number; symbol: string } placeholder?: string minTokenAmount?: string | number @@ -44,7 +43,6 @@ export const TokenAmountFormBase: FC< isDisabled = false, shouldValidateForm = true, shouldDisplayMaxAmountInLabel = false, - shouldDisableButton = true, placeholder, submitButtonVariant = "solid", ...formikProps @@ -81,7 +79,7 @@ export const TokenAmountFormBase: FC< mt="6" submitText={submitButtonText} variant={submitButtonVariant} - isDisabled={shouldDisableButton && isDisabled} + isDisabled={isDisabled} /> ) diff --git a/src/components/SubmitTxButton.tsx b/src/components/SubmitTxButton.tsx index 5214d511f..2d0c5e558 100644 --- a/src/components/SubmitTxButton.tsx +++ b/src/components/SubmitTxButton.tsx @@ -32,6 +32,7 @@ const SubmitTxButton: FC = ({ onClick={() => openModal(ModalType.SelectWallet)} {...buttonProps} type="button" + isDisabled={false} > Connect Wallet diff --git a/src/pages/Staking/NewStakeCard.tsx b/src/pages/Staking/NewStakeCard.tsx index a28ac3d5b..41079048d 100644 --- a/src/pages/Staking/NewStakeCard.tsx +++ b/src/pages/Staking/NewStakeCard.tsx @@ -41,7 +41,6 @@ const NewStakeCard: FC> = () => { maxTokenAmount={tBalance} placeholder={placeholder} minTokenAmount={minStakeAmount} - shouldDisableButton={false} isDisabled={!active} />