From a11e67496732272e433f24fb7ad6436fc0ff90d3 Mon Sep 17 00:00:00 2001 From: Fouad Date: Wed, 6 Jul 2022 16:19:15 +0100 Subject: [PATCH] Wallet: Transfer token component with fee included (#1385) * set up transfer token component with fee included * open transfer modal after balance refresh * create fee warning component * use confirmation modal --- nym-wallet/src/components/FeeWarning.tsx | 16 +++ .../src/components/Rewards/CompoundModal.tsx | 20 ++-- .../src/components/Rewards/RedeemModal.tsx | 28 ++--- nym-wallet/src/context/mocks/main.tsx | 1 + nym-wallet/src/hooks/useGetBalance.ts | 8 +- .../balance/components/TransferModal.tsx | 101 ++++++++++++++++++ .../components/TransferModalSuccess.tsx | 26 +++++ nym-wallet/src/pages/balance/index.tsx | 34 ++++-- nym-wallet/src/pages/balance/types.ts | 2 + nym-wallet/src/pages/balance/vesting.tsx | 50 ++------- nym-wallet/src/requests/simulate.ts | 4 +- 11 files changed, 210 insertions(+), 80 deletions(-) create mode 100644 nym-wallet/src/components/FeeWarning.tsx create mode 100644 nym-wallet/src/pages/balance/components/TransferModal.tsx create mode 100644 nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx create mode 100644 nym-wallet/src/pages/balance/types.ts diff --git a/nym-wallet/src/components/FeeWarning.tsx b/nym-wallet/src/components/FeeWarning.tsx new file mode 100644 index 0000000000..b26a7f8369 --- /dev/null +++ b/nym-wallet/src/components/FeeWarning.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Warning } from '@mui/icons-material'; +import { FeeDetails } from '@nymproject/types'; +import { Alert, AlertTitle } from '@mui/material'; + +export const FeeWarning = ({ fee, amount }: { fee: FeeDetails; amount: number }) => { + if (fee.amount && +fee.amount.amount > amount) { + return ( + }> + Warning: fees are greater than the reward + The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue? + + ); + } + return null; +}; diff --git a/nym-wallet/src/components/Rewards/CompoundModal.tsx b/nym-wallet/src/components/Rewards/CompoundModal.tsx index 7b90e3a01b..cc779dc1db 100644 --- a/nym-wallet/src/components/Rewards/CompoundModal.tsx +++ b/nym-wallet/src/components/Rewards/CompoundModal.tsx @@ -1,13 +1,12 @@ import React, { useEffect } from 'react'; -import { Alert, AlertTitle, Stack, Typography } from '@mui/material'; -import WarningIcon from '@mui/icons-material/Warning'; +import { Stack, Typography } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { simulateCompoundDelgatorReward, simulateVestingCompoundDelgatorReward } from 'src/requests'; -import { isGreaterThan } from 'src/utils'; import { useGetFee } from 'src/hooks/useGetFee'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalFee } from '../Modals/ModalFee'; import { FeeDetails } from '@nymproject/types'; +import { FeeWarning } from '../FeeWarning'; export const CompoundModal: React.FC<{ open: boolean; @@ -57,13 +56,16 @@ export const CompoundModal: React.FC<{ Rewards will be transferred to account you are logged in with now + + theme.palette.nym.fee}> + Est. fee for this transaction: + + theme.palette.nym.fee}> + {fee} {currency} + + + {fee && } - {fee?.amount && isGreaterThan(+fee.amount.amount, amount) && ( - }> - Warning: fees are greater than the reward - The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue? - - )} ); }; diff --git a/nym-wallet/src/components/Rewards/RedeemModal.tsx b/nym-wallet/src/components/Rewards/RedeemModal.tsx index 8ac05d7ab3..0bb317c093 100644 --- a/nym-wallet/src/components/Rewards/RedeemModal.tsx +++ b/nym-wallet/src/components/Rewards/RedeemModal.tsx @@ -1,13 +1,12 @@ import React, { useEffect } from 'react'; -import { Alert, AlertTitle, Stack, Typography } from '@mui/material'; +import { Stack, Typography } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; -import WarningIcon from '@mui/icons-material/Warning'; -import { simulateClaimDelgatorReward, simulateVestingClaimDelgatorReward } from 'src/requests'; -import { isGreaterThan } from 'src/utils'; -import { useGetFee } from 'src/hooks/useGetFee'; -import { SimpleModal } from '../Modals/SimpleModal'; -import { ModalFee } from '../Modals/ModalFee'; import { FeeDetails } from '@nymproject/types'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { simulateClaimDelgatorReward, simulateVestingClaimDelgatorReward } from 'src/requests'; +import { ModalFee } from '../Modals/ModalFee'; +import { SimpleModal } from '../Modals/SimpleModal'; +import { FeeWarning } from '../FeeWarning'; export const RedeemModal: React.FC<{ open: boolean; @@ -58,13 +57,16 @@ export const RedeemModal: React.FC<{ Rewards will be transferred to account you are logged in with now + + theme.palette.nym.fee}> + Est. fee for this transaction: + + theme.palette.nym.fee}> + {fee} {currency} + + + {fee && } - {fee?.amount && isGreaterThan(+fee.amount.amount, amount) && ( - }> - Warning: fees are greater than the reward - The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue? - - )} ); }; diff --git a/nym-wallet/src/context/mocks/main.tsx b/nym-wallet/src/context/mocks/main.tsx index 5fefc649fb..672b3e0123 100644 --- a/nym-wallet/src/context/mocks/main.tsx +++ b/nym-wallet/src/context/mocks/main.tsx @@ -32,6 +32,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => { clearBalance: () => undefined, fetchBalance: async () => undefined, fetchTokenAllocation: async () => undefined, + refreshBalances: async () => {}, }, showAdmin: false, showTerminal: false, diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index 3c8015fe8f..56118351e5 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -35,6 +35,7 @@ export type TUseuserBalance = { fetchTokenAllocation: () => Promise; clearBalance: () => void; clearAll: () => void; + refreshBalances: () => Promise; }; export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { @@ -108,8 +109,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { clearOriginalVesting(); }; - const handleRefresh = async (addr?: string) => { - if (addr) { + const refreshBalances = async () => { + if (clientDetails?.client_address) { await fetchBalance(); await fetchTokenAllocation(); } else { @@ -118,7 +119,7 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { }; useEffect(() => { - handleRefresh(clientDetails?.client_address); + refreshBalances(); }, [clientDetails]); return { @@ -133,5 +134,6 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => { clearBalance, clearAll, fetchTokenAllocation, + refreshBalances, }; }; diff --git a/nym-wallet/src/pages/balance/components/TransferModal.tsx b/nym-wallet/src/pages/balance/components/TransferModal.tsx new file mode 100644 index 0000000000..8312b31ad0 --- /dev/null +++ b/nym-wallet/src/pages/balance/components/TransferModal.tsx @@ -0,0 +1,101 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Alert, Box, CircularProgress } from '@mui/material'; +import { FeeDetails } from '@nymproject/types'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { AppContext, urls } from 'src/context'; +import { FeeWarning } from 'src/components/FeeWarning'; +import { withdrawVestedCoins } from 'src/requests'; +import { Console } from 'src/utils/console'; +import { simulateWithdrawVestedCoins } from 'src/requests/simulate'; +import { SuccessModal } from './TransferModalSuccess'; +import { TResponseState, TTransactionDetails } from '../types'; + +export const TransferModal = ({ onClose }: { onClose: () => void }) => { + const [state, setState] = useState(); + const [fee, setFee] = useState(); + const [tx, setTx] = useState(); + + const { userBalance, clientDetails, network } = useContext(AppContext); + + const getFee = async () => { + if (userBalance.tokenAllocation?.spendable && clientDetails?.denom) { + try { + const simulatedFee = await simulateWithdrawVestedCoins({ + amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.denom }, + }); + setFee(simulatedFee); + } catch (e) { + setFee({ amount: { amount: 'n/a', denom: clientDetails.denom }, fee: { Auto: null } }); + Console.error(e); + } + } + }; + + useEffect(() => { + getFee(); + }, []); + + const handleTransfer = async () => { + if (userBalance.tokenAllocation?.spendable && clientDetails?.denom) { + setState('loading'); + try { + const txResponse = await withdrawVestedCoins({ + amount: userBalance.tokenAllocation?.spendable, + denom: clientDetails.denom, + }); + setState('success'); + setTx({ + amount: `${userBalance.tokenAllocation?.spendable} ${clientDetails?.denom}`, + url: `${urls(network).blockExplorer}/transaction/${txResponse.transaction_hash}`, + }); + await userBalance.refreshBalances(); + } catch (e) { + Console.error(e as string); + setState('fail'); + } + } + }; + + if (state === 'success') { + return ; + } + + return ( + + + {state === 'loading' ? ( + + + + ) : ( + <> + + } + divider + /> + {userBalance.tokenAllocation?.spendable && fee && ( + + )} + + )} + + {state === 'fail' && Transfer failed please try again in a few minutes} + + ); +}; diff --git a/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx b/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx new file mode 100644 index 0000000000..a4cba85844 --- /dev/null +++ b/nym-wallet/src/pages/balance/components/TransferModalSuccess.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { ConfirmationModal } from 'src/components'; +import { TTransactionDetails } from '../types'; + +export const SuccessModal = ({ tx, onClose }: { tx?: TTransactionDetails; onClose: () => void }) => ( + Transfer to balance successful} + onClose={onClose} + onConfirm={onClose} + maxWidth="xs" + fullWidth + confirmButton="Done" + > + + {tx && ( + <> + {tx.amount} + + + )} + + +); diff --git a/nym-wallet/src/pages/balance/index.tsx b/nym-wallet/src/pages/balance/index.tsx index 72a955db10..a51931fc84 100644 --- a/nym-wallet/src/pages/balance/index.tsx +++ b/nym-wallet/src/pages/balance/index.tsx @@ -1,15 +1,29 @@ -import React from 'react'; +import React, { useContext, useState } from 'react'; import { Box } from '@mui/material'; +import { AppContext } from '../../context/main'; + import { BalanceCard } from './balance'; import { VestingCard } from './vesting'; - import { PageLayout } from '../../layouts'; +import { TransferModal } from './components/TransferModal'; -export const Balance = () => ( - - - - - - -); +export const Balance = () => { + const [showTransferModal, setShowTransferModal] = useState(false); + + const { userBalance } = useContext(AppContext); + + const handleShowTransferModal = async () => { + await userBalance.refreshBalances(); + setShowTransferModal(true); + }; + + return ( + + + + + {showTransferModal && setShowTransferModal(false)} />} + + + ); +}; diff --git a/nym-wallet/src/pages/balance/types.ts b/nym-wallet/src/pages/balance/types.ts new file mode 100644 index 0000000000..5f88fe60b1 --- /dev/null +++ b/nym-wallet/src/pages/balance/types.ts @@ -0,0 +1,2 @@ +export type TResponseState = 'loading' | 'success' | 'fail'; +export type TTransactionDetails = { amount: string; url: string }; diff --git a/nym-wallet/src/pages/balance/vesting.tsx b/nym-wallet/src/pages/balance/vesting.tsx index a8ca344ad9..54dd112fe2 100644 --- a/nym-wallet/src/pages/balance/vesting.tsx +++ b/nym-wallet/src/pages/balance/vesting.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { Box, Button, - CircularProgress, Grid, IconButton, Table, @@ -15,12 +14,10 @@ import { } from '@mui/material'; import { InfoOutlined, Refresh } from '@mui/icons-material'; import { useSnackbar } from 'notistack'; -import { Fee, InfoTooltip, NymCard, Title } from '../../components'; -import { AppContext } from '../../context/main'; -import { withdrawVestedCoins } from '../../requests'; -import { Period } from '../../types'; +import { InfoTooltip, NymCard, Title } from 'src/components'; +import { AppContext } from 'src/context/main'; +import { Period } from 'src/types'; import { VestingTimeline } from './components/vesting-timeline'; -import { Console } from '../../utils/console'; const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [ { title: 'Locked', align: 'left' }, @@ -120,10 +117,8 @@ const TokenTransfer = () => { ); }; -export const VestingCard = () => { - const [isLoading, setIsLoading] = useState(false); - - const { userBalance, clientDetails } = useContext(AppContext); +export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise }) => { + const { userBalance } = useContext(AppContext); const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const refreshBalances = async () => { @@ -156,39 +151,8 @@ export const VestingCard = () => { > - - {userBalance.tokenAllocation?.spendable !== '0' ? :
} - diff --git a/nym-wallet/src/requests/simulate.ts b/nym-wallet/src/requests/simulate.ts index f224035f23..a9b41b41e7 100644 --- a/nym-wallet/src/requests/simulate.ts +++ b/nym-wallet/src/requests/simulate.ts @@ -50,5 +50,5 @@ export const simulateVestingUnbondMixnode = async (args: any) => export const simulateVestingUpdateMixnode = async (args: any) => invokeWrapper('simulate_vesting_update_mixnode', args); -export const simulateWithdrawVestedCoins = async (args: any) => - invokeWrapper('simulate_withdraw_vested_coins', args); +export const simulateWithdrawVestedCoins = async ({ amount }: { amount: MajorCurrencyAmount }) => + invokeWrapper('simulate_withdraw_vested_coins', { amount });