diff --git a/src/components/UI/components/TabPanel.js b/src/components/UI/components/TabPanel.js new file mode 100644 index 0000000..90490fd --- /dev/null +++ b/src/components/UI/components/TabPanel.js @@ -0,0 +1,19 @@ +import { Box } from '@mui/material'; + +const TabPanel = (props) => { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +export default TabPanel; \ No newline at end of file diff --git a/src/pages/SendTokens/SendTokens.js b/src/pages/SendTokens/SendTokens.js index 41febfc..eda96e9 100644 --- a/src/pages/SendTokens/SendTokens.js +++ b/src/pages/SendTokens/SendTokens.js @@ -1,5 +1,5 @@ -import React, { useContext, useState } from 'react'; -import { Paper } from '@mui/material'; +import { useContext, useState } from 'react'; +import { Paper, Tab, Tabs } from '@mui/material'; import { ContentContainer, LoaderContainer, @@ -10,23 +10,36 @@ import SendTokensForm from './SendTokensForm/SendTokensForm'; import Message, { MessageType, } from '../../components/UI/components/Message/Message'; -import apiClient from '../../utils/apiClient'; import { Loader } from '../../components/UI/components/Loader/Loader'; import TokenInfoBlock from './TokenInfoBlock/TokenInfoBlock'; -import { formatWithCommas } from '../../utils/formatting'; +import SendToUntrustedWalletsForm from './SendTokensForm/SendToUntrustedWallets'; import AuthContext from '../../store/auth-context'; +import TabPanel from '../../components/UI/components/TabPanel' +import { handleCreateWallet } from './helpers/walletHandlers'; +import { formatWithCommas } from '../../utils/formatting'; +import { handleSendToUntrustedWallets } from './helpers/sendTokenHandlers'; +import apiClient from '../../utils/apiClient'; + + const SendTokens = () => { const [createdWalletName, setCreatedWalletName] = useState(); const [errorMessage, setErrorMessage] = useState(); const [successMessage, setSuccessMessage] = useState(); const [isLoading, setIsLoading] = useState(false); + const [tabValue, setTabValue] = useState(0); const [senderWalletName, setSenderWalletName] = useState(); const [senderWalletTokens, setSenderWalletTokens] = useState(0); const authContext = useContext(AuthContext); + const handleTabChange = (event, newValue) => { + setTabValue(newValue); + }; + + + // TODO: uncomment when API is ready: is should have a totalTokens value // const [totalTokensAmount, setTotalTokensAmount] = useState(); @@ -101,34 +114,14 @@ const SendTokens = () => { }); }; - const handleCreateWalled = (name) => { - if (!name) return; - - setIsLoading(true); + - apiClient - .setAuthHeader(authContext.token) - .post('/wallets', { - wallet: name, - }) - .then(() => { - setErrorMessage(''); - setSuccessMessage(`Wallet ${name} created successfully!`); - setCreatedWalletName(name); - }) - .catch((error) => { - console.error(error); - setSuccessMessage(''); - const errorMessage = - error.response.status === 403 && - error.response.data.message.includes('already exists') - ? 'Wallet with this name already exists.' - : 'An error occurred while creating a wallet.'; - setErrorMessage(errorMessage); - }) - .finally(() => { - setIsLoading(false); - }); + const callbacks = { + setIsLoading, + setErrorMessage, + setSuccessMessage, + setSenderWalletTokens, + setCreatedWalletName }; return ( @@ -158,38 +151,75 @@ const SendTokens = () => { width: '100%', height: '60vh', display: 'flex', - justifyContent: 'space-between', + flexDirection: 'column', }} > + + + + + + {isLoading && ( )} - { - if (!wallet) { - setSenderWalletName(null); - setSenderWalletTokens(null); - return; - } - - setSenderWalletName(wallet.name); - setSenderWalletTokens(wallet.tokensInWallet); - }} - /> - + + +
+ handleSendTokenForm(data, authContext, callbacks)} + createdWalletName={createdWalletName} + onCreateWallet={(name) => handleCreateWallet(name, authContext, callbacks)} + onSenderWalletSelected={(wallet) => { + if (!wallet) { + setSenderWalletName(null); + setSenderWalletTokens(null); + return; + } + setSenderWalletName(wallet.name); + setSenderWalletTokens(wallet.tokensInWallet); + }} + /> + +
+
+ + +
+

Under development

+ {/* To be implemented */} +
+
+ + +
+ handleSendToUntrustedWallets(data, authContext, callbacks)} + onSenderWalletSelected={(wallet) => { + if (!wallet) { + setSenderWalletName(null); + setSenderWalletTokens(null); + return; + } + setSenderWalletName(wallet?.name); + setSenderWalletTokens(wallet?.tokensInWallet); + }} + /> + +
+
); }; -export default SendTokens; +export default SendTokens; \ No newline at end of file diff --git a/src/pages/SendTokens/SendTokensForm/SendToUntrustedWallets.js b/src/pages/SendTokens/SendTokensForm/SendToUntrustedWallets.js new file mode 100644 index 0000000..75fa171 --- /dev/null +++ b/src/pages/SendTokens/SendTokensForm/SendToUntrustedWallets.js @@ -0,0 +1,137 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Grid, TextField } from '@mui/material'; +import SelectWallet from './SelectWallet'; +import { StyledBox, StyledButton } from './SendTokensFormStyled'; +import ConfirmDialog from './confirmDialog/ConfirmDialog'; + +const SendToUntrustedWalletsForm = (props) => { + const { + onSubmit, + onSenderWalletSelected, + } = props; + + const [senderWallet, setSenderWallet] = useState(null); + const [receiverWallet, setReceiverWallet] = useState(''); + const tokensAmountRef = useRef(0); + const [isSubmitBtnDisabled, setIsSubmitButtonDisabled] = useState(true); + const [showConfirmationDialog, setShowConfirmationDialog] = useState(false); + + useEffect(() => { + isSubmitButtonDisabled(); + }, [receiverWallet, senderWallet]); + + const handleConfirmationDialogOpen = e => { + e.preventDefault(); + setShowConfirmationDialog(true); + }; + + const handleConfirmationDialogClose = () => { + setShowConfirmationDialog(false); + }; + + const handleConfirmSubmit = () => { + handleSubmit(); + handleConfirmationDialogClose(); + }; + + const handleChangeSenderWallet = useCallback((wallet) => { + if (!wallet) { + setSenderWallet(null); + onSenderWalletSelected(null); + return; + } + + setSenderWallet(wallet.name); + onSenderWalletSelected(wallet); + + if (tokensAmountRef.current.value > wallet.tokensInWallet) { + tokensAmountRef.current.value = wallet.tokensInWallet; + } + tokensAmountRef.current.setAttribute('max', wallet.tokensInWallet); + }, []); + + const handleReceiverWalletChange = (e) => { + setReceiverWallet(e.target.value); + }; + + const handleSubmit = () => { + const tokensAmount = tokensAmountRef.current.value; + + onSubmit({ + senderWallet, + receiverWallet, + tokensAmount, + }); + + // reset form + tokensAmountRef.current.value = 1; + }; + + const isSubmitButtonDisabled = () => { + const isDisabled = + !senderWallet || + !receiverWallet || + tokensAmountRef.current.value <= 0; + setIsSubmitButtonDisabled(isDisabled); + }; + + return ( + <> + +
+ + + + + + + + + + + isSubmitButtonDisabled()} + /> + + + + Submit + + + +
+
+ + + ); +}; + +export default React.memo(SendToUntrustedWalletsForm); \ No newline at end of file diff --git a/src/pages/SendTokens/helpers/sendTokenHandlers.js b/src/pages/SendTokens/helpers/sendTokenHandlers.js new file mode 100644 index 0000000..e5eb178 --- /dev/null +++ b/src/pages/SendTokens/helpers/sendTokenHandlers.js @@ -0,0 +1,89 @@ +import apiClient from "../../../utils/apiClient"; + + + +export const handleSendTokenForm = async (data, authContext, callbacks) => { + const { setIsLoading, setErrorMessage, setSuccessMessage, setSenderWalletTokens } = callbacks; + + setIsLoading(true); + + try { + const response = await apiClient + .setAuthHeader(authContext.token) + .post('/transfers', { + bundle: { bundle_size: data.tokensAmount }, + sender_wallet: data.senderWallet, + receiver_wallet: data.receiverWallet, + claim: false, + }); + + console.log( + 'Tokens transfer completed. Response: ' + JSON.stringify(response) + ); + + setSenderWalletTokens((prev) => prev - data.tokensAmount); + setErrorMessage(''); + setSuccessMessage( + `${data.tokensAmount} tokens were successfully sent from '${data.senderWallet}' to '${data.receiverWallet}' wallet. Status of the transfer: '${response.data.state}'` + ); + } catch (error) { + console.error(error); + setSuccessMessage(''); + const errorMessage = + error.response?.data?.message === + 'Cannot transfer to the same wallet as the originating one!' + ? error.response.data.message + : 'An error occurred while sending tokens.'; + setErrorMessage(errorMessage); + } finally { + setIsLoading(false); + } +}; + +export const handleSendToUntrustedWallets = async (data, authContext, callbacks) => { + const { setIsLoading, setErrorMessage, setSuccessMessage, setSenderWalletTokens } = callbacks; + + setIsLoading(true); + + try { + const response = await apiClient + .setAuthHeader(authContext.token) + .post('/transfers', { + bundle: { bundle_size: data.tokensAmount }, + sender_wallet: data.senderWallet, + receiver_wallet: data.receiverWallet, + claim: false, + }); + + console.log( + 'Tokens transfer to untrusted wallet completed. Response: ' + JSON.stringify(response) + ); + + setSenderWalletTokens((prev) => prev - data.tokensAmount); + setErrorMessage(''); + setSuccessMessage( + `${data.tokensAmount} tokens were successfully sent to untrusted wallet '${data.receiverWallet}'. ` + + `Status: '${response.data.state}'. ` + + `Note: This wallet is not trusted - please verify with the recipient.` + ); + } catch (error) { + console.error('Error sending to untrusted wallet:', error); + setSuccessMessage(''); + + let errorMessage = 'An error occurred while sending tokens to untrusted wallet.'; + + if (error.response) { + if (error.response.data.message === 'Cannot transfer to the same wallet as the originating one!') { + errorMessage = error.response.data.message; + } else if (error.response.data.message.includes('invalid wallet address')) { + errorMessage = 'Invalid wallet address format. Please check the receiver wallet address.'; + } else if (error.response.status === 403) { + errorMessage = 'You are not authorized to send to this wallet.'; + } + } + + setErrorMessage(errorMessage); + } finally { + setIsLoading(false); + } +}; \ No newline at end of file diff --git a/src/pages/SendTokens/helpers/walletHandlers.js b/src/pages/SendTokens/helpers/walletHandlers.js new file mode 100644 index 0000000..a81cb22 --- /dev/null +++ b/src/pages/SendTokens/helpers/walletHandlers.js @@ -0,0 +1,33 @@ +import apiClient from "../../../utils/apiClient"; + + +export const handleCreateWallet = async (name, authContext, callbacks) => { + const { setIsLoading, setErrorMessage, setSuccessMessage, setCreatedWalletName } = callbacks; + + if (!name) return; + + setIsLoading(true); + + try { + await apiClient + .setAuthHeader(authContext.token) + .post('/wallets', { + wallet: name, + }); + + setErrorMessage(''); + setSuccessMessage(`Wallet ${name} created successfully!`); + setCreatedWalletName(name); + } catch (error) { + console.error(error); + setSuccessMessage(''); + const errorMessage = + error.response?.status === 403 && + error.response?.data?.message?.includes('already exists') + ? 'Wallet with this name already exists.' + : 'An error occurred while creating a wallet.'; + setErrorMessage(errorMessage); + } finally { + setIsLoading(false); + } +}; \ No newline at end of file