From 8b9c763c84066e647ae93c39f0f530f3c32f4533 Mon Sep 17 00:00:00 2001 From: Ramin Date: Mon, 27 Sep 2021 23:20:08 +0330 Subject: [PATCH 1/4] fix trace view not unsubscribing --- src/services/TraceService.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/services/TraceService.js b/src/services/TraceService.js index 272b8082d..b5f107781 100644 --- a/src/services/TraceService.js +++ b/src/services/TraceService.js @@ -82,7 +82,8 @@ class TraceService { } else if (slug) { query.slug = slug; } - traces + + return traces .watch({ listStrategy: 'always' }) .find({ query }) .subscribe(resp => { @@ -971,19 +972,21 @@ class TraceService { * Withdraw the donations (pledges) from a trace * Only possible when the traces was approved for completion * - * @param trace a Trace model + * @param trace a Trace model * @param from (string) Ethereum address * @param onTxHash Callback function once the transaction was created * @param onConfirmation Callback function once the transaction was mined * @param onError Callback function if error is encountered + * @param selectedToken Selected Token to withdraw * @param web3 */ - static withdraw({ trace, from, onTxHash, onConfirmation, onError, web3 }) { + static withdraw({ trace, from, onTxHash, onConfirmation, onError, web3, selectedTokens }) { let txHash; - - DonationBlockchainService.getTraceDonations(trace._id) + DonationBlockchainService.getTraceDonations(trace._id, selectedTokens) .then(data => { + // console.log(data); + const traceContract = trace.contract(web3); const execute = opts => { From 6503e6edab25057c4835db9bf485a1f1fe9adb7a Mon Sep 17 00:00:00 2001 From: Ramin Date: Mon, 27 Sep 2021 23:24:46 +0330 Subject: [PATCH 2/4] 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; From 1f90f6764e9217728ed809670c14e8af8cc769e9 Mon Sep 17 00:00:00 2001 From: Ramin Date: Mon, 27 Sep 2021 23:51:45 +0330 Subject: [PATCH 3/4] show error when no token selected --- src/components/WithdrawTraceFundsButton.jsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/WithdrawTraceFundsButton.jsx b/src/components/WithdrawTraceFundsButton.jsx index 8c552fd48..75abcfcf0 100644 --- a/src/components/WithdrawTraceFundsButton.jsx +++ b/src/components/WithdrawTraceFundsButton.jsx @@ -1,6 +1,6 @@ import React, { Fragment, useContext, useRef } from 'react'; import PropTypes from 'prop-types'; -import { Modal, Select } from 'antd'; +import { Modal, notification, Select } from 'antd'; import TraceService from 'services/TraceService'; import Trace from 'models/Trace'; @@ -149,8 +149,14 @@ const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw, withdrawal okText: 'Withdrawal', centered: true, width: 500, - onOk: () => - TraceService.withdraw({ + onOk: () => { + if (selectedTokens.current.length < 1) { + return notification.error({ + message: 'No token selected!', + description: 'Select at least one token to withdraw.', + }); + } + return TraceService.withdraw({ web3, trace, from: userAddress, @@ -171,7 +177,8 @@ const WithdrawTraceFundsButton = ({ trace, isAmountEnoughForWithdraw, withdrawal // TODO: need to update feathers to reset the donations to previous state as this else displayTransactionError(txUrl); }, - }), + }); + }, }); }) .catch(err => From 9b92119315ace98386b66bec0049c3125dd7eb3f Mon Sep 17 00:00:00 2001 From: Ramin Date: Tue, 28 Sep 2021 21:45:32 +0330 Subject: [PATCH 4/4] fix withdrawal for my-traces view --- src/components/TraceActions.jsx | 23 +++++++++++++++-------- src/components/views/MyTraces.jsx | 7 ++----- src/styles/_antOverrides.scss | 8 ++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/components/TraceActions.jsx b/src/components/TraceActions.jsx index 07714cadd..55164627d 100644 --- a/src/components/TraceActions.jsx +++ b/src/components/TraceActions.jsx @@ -24,13 +24,15 @@ function TraceActions({ trace }) { actions: { convertMultipleRates }, } = useContext(ConversionRateContext); const { - state: { minimumPayoutUsdValue }, + state: { minimumPayoutUsdValue, activeTokenWhitelist }, } = useContext(WhiteListContext); const { state: { currentUser }, } = useContext(UserContext); const [currentBalanceUsdValue, setCurrentBalanceUsdValue] = useState(0); - const [isAmountEnoughForWithdraw, setIsAmountEnoughForWithdraw] = useState(true); + const [withdrawalTokens, setWithdrawalTokens] = useState([]); + + const isAmountEnoughForWithdraw = withdrawalTokens.length > 0; const calculateTraceCurrentBalanceUsdValue = async () => { try { @@ -55,16 +57,20 @@ function TraceActions({ trace }) { 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]); useEffect(() => { @@ -107,6 +113,7 @@ function TraceActions({ trace }) { diff --git a/src/components/views/MyTraces.jsx b/src/components/views/MyTraces.jsx index f52cd0ee4..3608889fe 100644 --- a/src/components/views/MyTraces.jsx +++ b/src/components/views/MyTraces.jsx @@ -2,17 +2,15 @@ import React, { useState, useEffect, useContext, useCallback, Fragment, useRef } import { Link } from 'react-router-dom'; import moment from 'moment'; import Pagination from 'react-js-pagination'; +import { Helmet } from 'react-helmet'; import ViewNetworkWarning from 'components/ViewNetworkWarning'; +import TraceActions from 'components/TraceActions'; import { Context as Web3Context } from 'contextProviders/Web3Provider'; import { Context as WhiteListContext } from 'contextProviders/WhiteListProvider'; -import TraceActions from 'components/TraceActions'; -import { Helmet } from 'react-helmet'; import { Context as UserContext } from '../../contextProviders/UserProvider'; - import AuthenticationWarning from '../AuthenticationWarning'; import Loader from '../Loader'; - import { getTruncatedText, getReadableStatus, @@ -20,7 +18,6 @@ import { ANY_TOKEN, } from '../../lib/helpers'; import config from '../../configuration'; - import TraceService from '../../services/TraceService'; import Trace from '../../models/Trace'; import ErrorHandler from '../../lib/ErrorHandler'; diff --git a/src/styles/_antOverrides.scss b/src/styles/_antOverrides.scss index 6b3f2e3c1..56c8bda49 100644 --- a/src/styles/_antOverrides.scss +++ b/src/styles/_antOverrides.scss @@ -154,6 +154,14 @@ .ant-select-selection-item { line-height: 30px !important; height: 30px; + border-radius: 20px; + font-size: 14px; + padding-left: 13px; + padding-right: 13px; + } + .ant-select-selection-item-remove { + margin-top: -3px; + margin-left: 5px; } }