From fd89b6dfbf65586b112eeaef69ccc733d8ac07c2 Mon Sep 17 00:00:00 2001 From: Leandro Date: Thu, 20 Jan 2022 16:36:04 -0800 Subject: [PATCH 1/4] Added redux boilerplate for storing invest errors --- src/custom/state/claim/actions.ts | 6 +++++- src/custom/state/claim/hooks/index.ts | 3 +++ src/custom/state/claim/reducer.ts | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/custom/state/claim/actions.ts b/src/custom/state/claim/actions.ts index 85c037b01..01bd3582d 100644 --- a/src/custom/state/claim/actions.ts +++ b/src/custom/state/claim/actions.ts @@ -25,6 +25,7 @@ export type ClaimActions = { setInvestFlowStep: (payload: number) => void initInvestFlowData: () => void updateInvestAmount: (payload: { index: number; amount: string }) => void + updateInvestError: (payload: { index: number; error: string | undefined }) => void // claim row selection setSelected: (payload: number[]) => void @@ -51,7 +52,10 @@ export const updateInvestAmount = createAction<{ index: number amount: string }>('claim/updateInvestAmount') - +export const updateInvestError = createAction<{ + index: number + error: string | undefined +}>('claim/updateInvestError') // claim row selection export const setSelected = createAction('claim/setSelected') export const setSelectedAll = createAction('claim/setSelectedAll') diff --git a/src/custom/state/claim/hooks/index.ts b/src/custom/state/claim/hooks/index.ts index 70bda0727..d5ce0f0aa 100644 --- a/src/custom/state/claim/hooks/index.ts +++ b/src/custom/state/claim/hooks/index.ts @@ -53,6 +53,7 @@ import { setSelectedAll, ClaimStatus, resetClaimUi, + updateInvestError, } from '../actions' import { EnhancedUserClaimData } from 'pages/Claim/types' import { supportedChainId } from 'utils/supportedChainId' @@ -767,6 +768,8 @@ export function useClaimDispatchers() { setInvestFlowStep: (payload: number) => dispatch(setInvestFlowStep(payload)), initInvestFlowData: () => dispatch(initInvestFlowData()), updateInvestAmount: (payload: { index: number; amount: string }) => dispatch(updateInvestAmount(payload)), + updateInvestError: (payload: { index: number; error: string | undefined }) => + dispatch(updateInvestError(payload)), // claim row selection setSelected: (payload: number[]) => dispatch(setSelected(payload)), setSelectedAll: (payload: boolean) => dispatch(setSelectedAll(payload)), diff --git a/src/custom/state/claim/reducer.ts b/src/custom/state/claim/reducer.ts index 4f308437c..6f48b3cdb 100644 --- a/src/custom/state/claim/reducer.ts +++ b/src/custom/state/claim/reducer.ts @@ -14,6 +14,7 @@ import { setSelectedAll, resetClaimUi, ClaimStatus, + updateInvestError, } from './actions' export const initialState: ClaimState = { @@ -39,6 +40,7 @@ export const initialState: ClaimState = { export type InvestClaim = { index: number investedAmount: string + error?: string } export type ClaimState = { @@ -101,6 +103,9 @@ export default createReducer(initialState, (builder) => .addCase(updateInvestAmount, (state, { payload: { index, amount } }) => { state.investFlowData[index].investedAmount = amount }) + .addCase(updateInvestError, (state, { payload: { index, error } }) => { + state.investFlowData[index].error = error + }) .addCase(setSelected, (state, { payload }) => { state.selected = payload }) From 963c4f8ea923e1f84b1c62d4bec25f4570bd80b3 Mon Sep 17 00:00:00 2001 From: Leandro Date: Thu, 20 Jan 2022 16:37:12 -0800 Subject: [PATCH 2/4] Storing invest errors on redux rather than locally --- .../Claim/InvestmentFlow/InvestOption.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index a7c4b6a0f..ea84694cb 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -28,7 +28,7 @@ enum ErrorMsgs { export default function InvestOption({ approveData, claim, optionIndex }: InvestOptionProps) { const { currencyAmount, price, cost: maxCost } = claim - const { updateInvestAmount } = useClaimDispatchers() + const { updateInvestAmount, updateInvestError } = useClaimDispatchers() const { investFlowData, activeClaimAccount } = useClaimState() const { handleSetError, handleCloseError, ErrorModal } = useErrorModal() @@ -37,9 +37,9 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest const [percentage, setPercentage] = useState('0') const [typedValue, setTypedValue] = useState('0') - const [inputError, setInputError] = useState('') const investedAmount = investFlowData[optionIndex].investedAmount + const inputError = investFlowData[optionIndex].error const token = currencyAmount?.currency const balance = useCurrencyBalance(account || undefined, token) @@ -58,16 +58,16 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest updateInvestAmount({ index: optionIndex, amount }) setTypedValue(value.toExact() || '') - setInputError('') + updateInvestError({ index: optionIndex, error: undefined }) setPercentage(_formatPercentage(calculatePercentage(balance, maxCost))) - }, [balance, maxCost, noBalance, optionIndex, updateInvestAmount]) + }, [balance, maxCost, noBalance, optionIndex, updateInvestAmount, updateInvestError]) // on input field change handler const onInputChange = useCallback( (value: string) => { setTypedValue(value) - setInputError('') + updateInvestError({ index: optionIndex, error: undefined }) // parse to CurrencyAmount const parsedAmount = tryParseAmount(value, token) @@ -85,7 +85,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest else if (parsedAmount.greaterThan(balance)) errorMsg = ErrorMsgs.InsufficientBalance if (errorMsg) { - setInputError(errorMsg) + updateInvestError({ index: optionIndex, error: errorMsg }) updateInvestAmount({ index: optionIndex, amount: '0' }) setPercentage('0') return @@ -97,7 +97,7 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest // update the local state with percentage value setPercentage(_formatPercentage(calculatePercentage(parsedAmount, maxCost))) }, - [balance, maxCost, optionIndex, token, updateInvestAmount] + [balance, maxCost, optionIndex, token, updateInvestAmount, updateInvestError] ) // Cache approveData methods @@ -137,12 +137,12 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest } if (balance.lessThan(maxCost)) { - setInputError(ErrorMsgs.InsufficientBalance) + updateInvestError({ index: optionIndex, error: ErrorMsgs.InsufficientBalance }) } else { setMaxAmount() } } - }, [balance, isSelfClaiming, maxCost, setMaxAmount]) + }, [balance, isSelfClaiming, maxCost, optionIndex, setMaxAmount, updateInvestError]) // this will set input and percentage value if you go back from the review page useEffect(() => { From 464e1095bf24d501befe0a8722e14150e4d4c068 Mon Sep 17 00:00:00 2001 From: Leandro Date: Thu, 20 Jan 2022 16:37:41 -0800 Subject: [PATCH 3/4] Disable review button when there's any error on investment approval --- src/custom/pages/Claim/FooterNavButtons.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/custom/pages/Claim/FooterNavButtons.tsx b/src/custom/pages/Claim/FooterNavButtons.tsx index 7045543fd..a913a5bef 100644 --- a/src/custom/pages/Claim/FooterNavButtons.tsx +++ b/src/custom/pages/Claim/FooterNavButtons.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { Trans } from '@lingui/macro' import { isAddress } from '@ethersproject/address' import { useClaimDispatchers, useClaimState } from 'state/claim/hooks' @@ -33,6 +34,7 @@ export default function FooterNavButtons({ // claiming claimStatus, // investment + investFlowData, investFlowStep, isInvestFlowActive, // table select change @@ -45,6 +47,10 @@ export default function FooterNavButtons({ setIsInvestFlowActive, } = useClaimDispatchers() + const hasError = useMemo(() => { + return investFlowData.some(({ error }) => Boolean(error)) + }, [investFlowData]) + const isInputAddressValid = isAddress(resolvedAddress || '') // User is connected and has some unclaimed claims @@ -86,7 +92,7 @@ export default function FooterNavButtons({ Approve tokens ) : investFlowStep === 1 ? ( - setInvestFlowStep(2)}> + setInvestFlowStep(2)} disabled={hasError}> Review ) : ( From 5bbe2d55ce2e2bbb858473594daa598a8a5c767d Mon Sep 17 00:00:00 2001 From: Leandro Date: Fri, 21 Jan 2022 08:36:57 -0800 Subject: [PATCH 4/4] Refactored how global state is updated on InvestOption component --- .../Claim/InvestmentFlow/InvestOption.tsx | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx index ea84694cb..e211dcab8 100644 --- a/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx +++ b/src/custom/pages/Claim/InvestmentFlow/InvestOption.tsx @@ -41,6 +41,20 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest const investedAmount = investFlowData[optionIndex].investedAmount const inputError = investFlowData[optionIndex].error + // Syntactic sugar fns for setting/resetting global state + const setInvestedAmount = useCallback( + (amount: string) => updateInvestAmount({ index: optionIndex, amount }), + [optionIndex, updateInvestAmount] + ) + const setInputError = useCallback( + (error: string) => updateInvestError({ index: optionIndex, error }), + [optionIndex, updateInvestError] + ) + const resetInputError = useCallback( + () => updateInvestError({ index: optionIndex, error: undefined }), + [optionIndex, updateInvestError] + ) + const token = currencyAmount?.currency const balance = useCurrencyBalance(account || undefined, token) @@ -56,25 +70,25 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest const value = maxCost.greaterThan(balance) ? balance : maxCost const amount = value.quotient.toString() - updateInvestAmount({ index: optionIndex, amount }) + setInvestedAmount(amount) setTypedValue(value.toExact() || '') - updateInvestError({ index: optionIndex, error: undefined }) + resetInputError() setPercentage(_formatPercentage(calculatePercentage(balance, maxCost))) - }, [balance, maxCost, noBalance, optionIndex, updateInvestAmount, updateInvestError]) + }, [balance, maxCost, noBalance, resetInputError, setInvestedAmount]) // on input field change handler const onInputChange = useCallback( (value: string) => { setTypedValue(value) - updateInvestError({ index: optionIndex, error: undefined }) + resetInputError() // parse to CurrencyAmount const parsedAmount = tryParseAmount(value, token) // no amount/necessary params, return 0 if (!parsedAmount || !maxCost || !balance || !token) { - updateInvestAmount({ index: optionIndex, amount: '0' }) + setInvestedAmount('0') setPercentage('0') return } @@ -85,19 +99,19 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest else if (parsedAmount.greaterThan(balance)) errorMsg = ErrorMsgs.InsufficientBalance if (errorMsg) { - updateInvestError({ index: optionIndex, error: errorMsg }) - updateInvestAmount({ index: optionIndex, amount: '0' }) + setInputError(errorMsg) + setInvestedAmount('0') setPercentage('0') return } // update redux state with new investAmount value - updateInvestAmount({ index: optionIndex, amount: parsedAmount.quotient.toString() }) + setInvestedAmount(parsedAmount.quotient.toString()) // update the local state with percentage value setPercentage(_formatPercentage(calculatePercentage(parsedAmount, maxCost))) }, - [balance, maxCost, optionIndex, token, updateInvestAmount, updateInvestError] + [balance, maxCost, resetInputError, setInputError, setInvestedAmount, token] ) // Cache approveData methods @@ -137,12 +151,12 @@ export default function InvestOption({ approveData, claim, optionIndex }: Invest } if (balance.lessThan(maxCost)) { - updateInvestError({ index: optionIndex, error: ErrorMsgs.InsufficientBalance }) + setInputError(ErrorMsgs.InsufficientBalance) } else { setMaxAmount() } } - }, [balance, isSelfClaiming, maxCost, optionIndex, setMaxAmount, updateInvestError]) + }, [balance, isSelfClaiming, maxCost, optionIndex, setInputError, setMaxAmount]) // this will set input and percentage value if you go back from the review page useEffect(() => {