From 6503e6edab25057c4835db9bf485a1f1fe9adb7a Mon Sep 17 00:00:00 2001 From: Ramin Date: Mon, 27 Sep 2021 23:24:46 +0330 Subject: [PATCH] F #2544 ability to withdraw any tokens bigger than withdraw limit --- src/components/TraceConversationAction.jsx | 10 ++++- src/components/TraceConversationItem.jsx | 13 +++++- src/components/TraceConversations.jsx | 5 ++- src/components/ViewTraceAlerts.jsx | 8 +++- src/components/WithdrawTraceFundsButton.jsx | 50 ++++++++++++++++++--- src/components/views/ViewTrace.jsx | 21 ++++++--- src/components/views/verification/Main.jsx | 3 +- src/services/DonationBlockchainService.jsx | 7 ++- src/styles/_antOverrides.scss | 11 +++++ 9 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/components/TraceConversationAction.jsx b/src/components/TraceConversationAction.jsx index 1f2809f0c..18acd3414 100644 --- a/src/components/TraceConversationAction.jsx +++ b/src/components/TraceConversationAction.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/prefer-stateless-function */ -// @dev: not prefering stateless here because functionality will be extended +// @dev: not preferring stateless here because functionality will be extended import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; @@ -16,7 +16,7 @@ import LPTrace from '../models/LPTrace'; class TraceConversationAction extends Component { render() { - const { messageContext, trace, isAmountEnoughForWithdraw } = this.props; + const { messageContext, trace, isAmountEnoughForWithdraw, withdrawalTokens } = this.props; switch (messageContext) { case 'proposed': @@ -38,6 +38,7 @@ class TraceConversationAction extends Component { ); @@ -68,6 +69,11 @@ TraceConversationAction.propTypes = { ).isRequired, messageContext: PropTypes.string.isRequired, isAmountEnoughForWithdraw: PropTypes.bool.isRequired, + withdrawalTokens: PropTypes.arrayOf(PropTypes.shape({})), +}; + +TraceConversationAction.defaultProps = { + withdrawalTokens: [], }; export default React.memo(TraceConversationAction); diff --git a/src/components/TraceConversationItem.jsx b/src/components/TraceConversationItem.jsx index 63a7ab96b..2f6558bca 100644 --- a/src/components/TraceConversationItem.jsx +++ b/src/components/TraceConversationItem.jsx @@ -143,7 +143,12 @@ const getEtherScanUrl = ({ messageContext }) => ? config.homeEtherscan : config.etherscan; -function TraceConversationItem({ conversation, trace, isAmountEnoughForWithdraw }) { +function TraceConversationItem({ + conversation, + trace, + isAmountEnoughForWithdraw, + withdrawalTokens, +}) { if (!conversation) return null; const { txHash, messageContext, message, performedByRole, createdAt, owner } = conversation; @@ -180,6 +185,7 @@ function TraceConversationItem({ conversation, trace, isAmountEnoughForWithdraw messageContext={messageContext} trace={trace} isAmountEnoughForWithdraw={isAmountEnoughForWithdraw} + withdrawalTokens={withdrawalTokens} /> @@ -195,6 +201,11 @@ TraceConversationItem.propTypes = { ).isRequired, conversation: PropTypes.instanceOf(Object).isRequired, isAmountEnoughForWithdraw: PropTypes.bool.isRequired, + withdrawalTokens: PropTypes.arrayOf(PropTypes.shape({})), +}; + +TraceConversationItem.defaultProps = { + withdrawalTokens: [], }; export default React.memo(TraceConversationItem); diff --git a/src/components/TraceConversations.jsx b/src/components/TraceConversations.jsx index 50fa16268..bdb4ee1b4 100644 --- a/src/components/TraceConversations.jsx +++ b/src/components/TraceConversations.jsx @@ -12,7 +12,7 @@ import BridgedTrace from '../models/BridgedTrace'; import LPPCappedTrace from '../models/LPPCappedTrace'; import LPTrace from '../models/LPTrace'; -const TraceConversations = ({ trace, maxHeight, isAmountEnoughForWithdraw }) => { +const TraceConversations = ({ trace, maxHeight, isAmountEnoughForWithdraw, withdrawalTokens }) => { const conversationsNumPerLoad = 5; const [conversations, setConversations] = useState({}); const [isLoading, setLoading] = useState(true); @@ -66,6 +66,7 @@ const TraceConversations = ({ trace, maxHeight, isAmountEnoughForWithdraw }) => conversation={conversation} trace={trace} isAmountEnoughForWithdraw={isAmountEnoughForWithdraw} + withdrawalTokens={withdrawalTokens} /> ))} @@ -85,10 +86,12 @@ TraceConversations.propTypes = { ).isRequired, maxHeight: PropTypes.string, isAmountEnoughForWithdraw: PropTypes.bool.isRequired, + withdrawalTokens: PropTypes.arrayOf(PropTypes.shape({})), }; TraceConversations.defaultProps = { maxHeight: 'unset', + withdrawalTokens: [], }; export default React.memo(TraceConversations); diff --git a/src/components/ViewTraceAlerts.jsx b/src/components/ViewTraceAlerts.jsx index ca76570a8..e06bbea85 100644 --- a/src/components/ViewTraceAlerts.jsx +++ b/src/components/ViewTraceAlerts.jsx @@ -18,7 +18,7 @@ import BridgedTrace from '../models/BridgedTrace'; import LPPCappedTrace from '../models/LPPCappedTrace'; import LPTrace from '../models/LPTrace'; -const ViewTraceAlerts = ({ trace, campaign, isAmountEnoughForWithdraw }) => { +const ViewTraceAlerts = ({ trace, campaign, isAmountEnoughForWithdraw, withdrawalTokens }) => { const { state: { currentUser, userIsCommunityOwner }, } = useContext(UserContext); @@ -92,6 +92,7 @@ const ViewTraceAlerts = ({ trace, campaign, isAmountEnoughForWithdraw }) => { )} @@ -105,6 +106,11 @@ ViewTraceAlerts.propTypes = { ).isRequired, campaign: PropTypes.instanceOf(Campaign).isRequired, isAmountEnoughForWithdraw: PropTypes.bool.isRequired, + withdrawalTokens: PropTypes.arrayOf(PropTypes.shape({})), +}; + +ViewTraceAlerts.defaultProps = { + withdrawalTokens: [], }; export default React.memo(ViewTraceAlerts); diff --git a/src/components/WithdrawTraceFundsButton.jsx b/src/components/WithdrawTraceFundsButton.jsx index 606166924..8c552fd48 100644 --- a/src/components/WithdrawTraceFundsButton.jsx +++ b/src/components/WithdrawTraceFundsButton.jsx @@ -1,16 +1,16 @@ -import React, { Fragment, useContext } from 'react'; +import React, { Fragment, useContext, useRef } from 'react'; import PropTypes from 'prop-types'; +import { Modal, Select } from 'antd'; import TraceService from 'services/TraceService'; import Trace from 'models/Trace'; import { authenticateUser, checkBalance } from 'lib/middleware'; -import { Modal } from 'antd'; import { Context as Web3Context } from '../contextProviders/Web3Provider'; import { Context as NotificationContext } from '../contextProviders/NotificationModalProvider'; +import { Context as UserContext } from '../contextProviders/UserProvider'; import DonationBlockchainService from '../services/DonationBlockchainService'; import LPTrace from '../models/LPTrace'; import config from '../configuration'; -import { Context as UserContext } from '../contextProviders/UserProvider'; import ErrorHandler from '../lib/ErrorHandler'; import BridgedTrace from '../models/BridgedTrace'; import LPPCappedTrace from '../models/LPPCappedTrace'; @@ -21,7 +21,7 @@ import { } from '../services/ConversionRateService'; import { displayTransactionError, txNotification } from '../lib/helpers'; -const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw }) => { +const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw, withdrawalTokens }) => { const { state: { currentUser }, } = useContext(UserContext); @@ -33,6 +33,8 @@ const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw }) => { actions: { minPayoutWarningInWithdraw }, } = useContext(NotificationContext); + const selectedTokens = useRef([]); + async function sendWithdrawAnalyticsEvent(txUrl) { const donationsCounters = trace.donationCounters.filter(dc => dc.currentBalance.gt(0)); // eslint-disable-next-line no-restricted-syntax @@ -85,6 +87,15 @@ const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw }) => { minPayoutWarningInWithdraw(); return; } + + let defaultValue; + if (withdrawalTokens.length === 1) { + const token = withdrawalTokens[0]; + // eslint-disable-next-line react/prop-types + defaultValue = token.address; + selectedTokens.current = defaultValue; + } + Modal.confirm({ title: isRecipient ? 'Withdrawal Funds to Wallet' : 'Disburse Funds to Recipient', content: ( @@ -111,16 +122,39 @@ const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw }) => { {isRecipient ? 'your' : "the recipient's"} wallet. )} + +
Select tokens to withdraw:
+ ), cancelText: 'Cancel', - okText: 'Yes, withdrawal', + okText: 'Withdrawal', centered: true, width: 500, onOk: () => TraceService.withdraw({ + web3, trace, from: userAddress, + selectedTokens: selectedTokens.current, onTxHash: txUrl => { sendWithdrawAnalyticsEvent(txUrl); txNotification('Initiating withdrawal from Trace...', txUrl, true); @@ -137,7 +171,6 @@ const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw }) => { // TODO: need to update feathers to reset the donations to previous state as this else displayTransactionError(txUrl); }, - web3, }), }); }) @@ -170,6 +203,11 @@ WithdrawTraceFundsButton.propTypes = { [Trace, BridgedTrace, LPPCappedTrace, LPTrace].map(PropTypes.instanceOf), ).isRequired, isAmountEnoughForWithdraw: PropTypes.bool.isRequired, + withdrawalTokens: PropTypes.arrayOf(PropTypes.shape({})), +}; + +WithdrawTraceFundsButton.defaultProps = { + withdrawalTokens: [], }; export default React.memo(WithdrawTraceFundsButton); diff --git a/src/components/views/ViewTrace.jsx b/src/components/views/ViewTrace.jsx index 9981578ab..d4bdb67b9 100644 --- a/src/components/views/ViewTrace.jsx +++ b/src/components/views/ViewTrace.jsx @@ -71,15 +71,16 @@ const ViewTrace = props => { const [trace, setTrace] = useState({}); const [communityTitle, setCommunityTitle] = useState(''); const [notFound, setNotFound] = useState(false); - const [isAmountEnoughForWithdraw, setIsAmountEnoughForWithdraw] = useState(true); const [currency, setCurrency] = useState(null); const [currentBalanceValue, setCurrentBalanceValue] = useState(0); const [currentBalanceUsdValue, setCurrentBalanceUsdValue] = useState(0); + const [withdrawalTokens, setWithdrawalTokens] = useState([]); const donationsObserver = useRef(); const traceSubscription = useRef(); const newDonations = useRef(0); const donationsPerBatch = 50; + const isAmountEnoughForWithdraw = withdrawalTokens.length > 0; const getCommunityTitle = async communityId => { if (communityId === 0) return; @@ -196,16 +197,20 @@ const ViewTrace = props => { if (!currentBalanceUsdValue) { return; } + const _withdrawalTokens = []; // eslint-disable-next-line no-restricted-syntax for (const currencyUsdValue of currentBalanceUsdValue) { - // if usdValue is zero we should not set setIsAmountEnoughForWithdraw(false) because we check - // minimumPayoutUsdValue comparison when usdValue for a currency is not zero - if (currencyUsdValue.usdValue && currencyUsdValue.usdValue < minimumPayoutUsdValue) { - setIsAmountEnoughForWithdraw(false); - return; + if (currencyUsdValue.usdValue >= minimumPayoutUsdValue) { + const token = activeTokenWhitelist.find( + _token => _token.symbol === currencyUsdValue.currency, + ); + _withdrawalTokens.push(token); } } - setIsAmountEnoughForWithdraw(true); + + if (_withdrawalTokens.length) { + setWithdrawalTokens(_withdrawalTokens); + } }, [currentBalanceUsdValue]); const isActiveTrace = () => { @@ -380,6 +385,7 @@ const ViewTrace = props => { trace={trace} campaign={campaign} isAmountEnoughForWithdraw={isAmountEnoughForWithdraw} + withdrawalTokens={withdrawalTokens} />
@@ -624,6 +630,7 @@ const ViewTrace = props => { trace={trace} isAmountEnoughForWithdraw={isAmountEnoughForWithdraw} maxHeight={`${detailsCardHeight}px`} + withdrawalTokens={withdrawalTokens} />
diff --git a/src/components/views/verification/Main.jsx b/src/components/views/verification/Main.jsx index 00c3d20a4..aff8f12f2 100644 --- a/src/components/views/verification/Main.jsx +++ b/src/components/views/verification/Main.jsx @@ -82,8 +82,7 @@ const Verification = props => { setStep(step + 1); }) .catch(err => { - if (err.message) ErrorHandler(err, err.message); - else ErrorHandler(err, 'Something went wrong!'); + ErrorHandler(err, err.message); }) .finally(() => setIsSaving(false)); }; diff --git a/src/services/DonationBlockchainService.jsx b/src/services/DonationBlockchainService.jsx index 595bf1a80..59f01c2e2 100644 --- a/src/services/DonationBlockchainService.jsx +++ b/src/services/DonationBlockchainService.jsx @@ -839,7 +839,7 @@ class DonationBlockchainService { .then(({ total }) => total); } - static async getTraceDonations(traceId) { + static async getTraceDonations(traceId, selectedTokens) { const service = feathersClient.service('/donations'); let data = []; let total; @@ -857,6 +857,11 @@ class DonationBlockchainService { $limit: spare || 1, $sort: { tokenAddress: 1, pledgeId: 1 }, // group by token }; + + if (selectedTokens) { + query.tokenAddress = { $in: selectedTokens }; + } + // eslint-disable-next-line no-await-in-loop const resp = await service.find({ query }); diff --git a/src/styles/_antOverrides.scss b/src/styles/_antOverrides.scss index 7eff2ec2f..6b3f2e3c1 100644 --- a/src/styles/_antOverrides.scss +++ b/src/styles/_antOverrides.scss @@ -146,6 +146,17 @@ } } +//Ant custom multi select with ant-select-custom-multiple class +.ant-select-custom-multiple { + .ant-select-selection-overflow { + margin-top: -3px; + } + .ant-select-selection-item { + line-height: 30px !important; + height: 30px; + } +} + .ant-picker { width: 100%; border: 2px solid #dfdae8;